From 284e89d95b8bd71265f61e8ed0e9a2bfb96958fc Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 28 Apr 2023 10:22:46 -0500 Subject: [PATCH 01/31] Add linking of classes and functions in See also lines. --- sphinxcontrib/mat_documenters.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 3fbe167..7b8d38e 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -171,6 +171,7 @@ def add_content(self, more_content, no_docstring=False): # autodoc-process-docstring is fired and can add some # content if desired docstrings.append([]) + docstrings = self.auto_link(docstrings) for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) @@ -179,6 +180,35 @@ def add_content(self, more_content, no_docstring=False): for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) + def auto_link(self, docstrings): + # autolink known names in See also + see_also_re = re.compile("See also\s+(.+)") + see_also_line = False + for i in range(len(docstrings)): + for j in range(len(docstrings[i])): + line = docstrings[i][j] + if line: # non-blank line + if not see_also_line and see_also_re.search(line): + see_also_line = True # line begins with "See also" + elif see_also_line: # blank line following see also section + see_also_line = False # end see also section + + if see_also_line: + for n, o in entities_table.items(): + if isinstance(o, MatClass): + role = "class" + elif isinstance(o, MatFunction): + role = "func" + else: + role = None + if role: + nn = n.replace("+", "") # remove + from name + # escape . and add negative look-behind for ` + pat = "(? Date: Fri, 28 Apr 2023 10:36:55 -0500 Subject: [PATCH 02/31] Add option `matlab_auto_link` Currently just True or False, default is False. In the future we may want to change this to a string with possible values like "see_also", "all" and maybe something inbetween, --- README.rst | 5 +++++ sphinxcontrib/mat_documenters.py | 3 ++- sphinxcontrib/matlab.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 33f0ade..62c523e 100644 --- a/README.rst +++ b/README.rst @@ -97,6 +97,11 @@ Additional Configuration If you want the closest to MATLAB documentation style, use ``matlab_short_links = True`` in your ``conf.py`` file. +``matlab_auto_link`` + Automatically link class and function names in docstring lines that begin + with "See also" (and any subsequent lines before a blank line). + Default is ``False``. *Added in Version 0.20.0*. + Roles and Directives -------------------- diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 7b8d38e..b5eb112 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -171,7 +171,8 @@ def add_content(self, more_content, no_docstring=False): # autodoc-process-docstring is fired and can add some # content if desired docstrings.append([]) - docstrings = self.auto_link(docstrings) + if self.env.config.matlab_auto_link: + docstrings = self.auto_link(docstrings) for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) diff --git a/sphinxcontrib/matlab.py b/sphinxcontrib/matlab.py index 1071802..2d668fe 100644 --- a/sphinxcontrib/matlab.py +++ b/sphinxcontrib/matlab.py @@ -863,6 +863,7 @@ def setup(app): app.add_config_value("matlab_keep_package_prefix", False, "env") app.add_config_value("matlab_show_property_default_value", False, "env") app.add_config_value("matlab_short_links", False, "env") + app.add_config_value("matlab_auto_link", False, "env") app.registry.add_documenter("mat:module", doc.MatModuleDocumenter) app.add_directive_to_domain( From 759b0f66beb993973fba860bdd708e5895a27806 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 28 Apr 2023 11:33:33 -0500 Subject: [PATCH 03/31] Add ref_role() method to MatObject Returns a string with the role to be used for references to this type of object (e.g. "class", "func", "meth", "attr"). --- sphinxcontrib/mat_types.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index 0206289..2df135e 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -191,6 +191,10 @@ def __init__(self, name): #: name of MATLAB object self.name = name + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "ref" + @property def __name__(self): return self.name @@ -430,6 +434,10 @@ def __init__(self, name, path, package): #: entities found in the module: class, function, module (subpath and +package) self.entities = [] + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "mod" + def safe_getmembers(self): logger.debug( f"[sphinxcontrib-matlabdomain] MatModule.safe_getmembers {self.name=}, {self.path=}, {self.package=}" @@ -842,6 +850,10 @@ def __init__(self, name, modname, tokens): if len(tks) > 0: self.rem_tks = tks # save extra tokens + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "func" + @property def __doc__(self): return self.docstring @@ -1306,6 +1318,10 @@ def __init__(self, name, modname, tokens): self.rem_tks = idx # index of last token + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "class" + def attributes(self, idx, attr_types): """ Retrieve MATLAB class, property and method attributes. @@ -1460,6 +1476,10 @@ def __init__(self, name, cls, attrs): self.docstring = attrs["docstring"] # self.class = attrs['class'] + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "attr" + @property def __doc__(self): return self.docstring @@ -1472,6 +1492,10 @@ def __init__(self, modname, tks, cls, attrs): self.cls = cls self.attrs = attrs + def ref_role(self): + # role to use for references to this object (e.g. when generating auto-links) + return "meth" + def skip_tokens(self): # Number of tokens to skip in `MatClass` num_rem_tks = len(self.rem_tks) From 05e5cf2af86fe327fdd9aed307eb57c6ad6d9f4c Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 28 Apr 2023 12:07:13 -0500 Subject: [PATCH 04/31] Add option to auto-link everywhere. Changed `matlab_auto_link` option to string value, currently with "see_also" or "all" as valid options. --- README.rst | 12 ++++-- sphinxcontrib/mat_documenters.py | 63 +++++++++++++++++++------------- sphinxcontrib/matlab.py | 2 +- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index 62c523e..e04a2bf 100644 --- a/README.rst +++ b/README.rst @@ -98,9 +98,15 @@ If you want the closest to MATLAB documentation style, use ``matlab_short_links = True`` in your ``conf.py`` file. ``matlab_auto_link`` - Automatically link class and function names in docstring lines that begin - with "See also" (and any subsequent lines before a blank line). - Default is ``False``. *Added in Version 0.20.0*. + Automatically convert the names of known classes and functions to links using + the ``:class:`` and ``:func:`` roles, respectively. Valid values are ``"see_also"`` + and ``"all"``. + * ``"see_also"`` - applies only to docstring lines that begin with "See also", + and any subsequent lines before the next blank line. + + * ``"all"`` - applies to all docstring lines. + + Default is ``None``. *Added in Version 0.20.0*. Roles and Directives diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index b5eb112..48573a5 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -183,31 +183,44 @@ def add_content(self, more_content, no_docstring=False): def auto_link(self, docstrings): # autolink known names in See also - see_also_re = re.compile("See also\s+(.+)") - see_also_line = False - for i in range(len(docstrings)): - for j in range(len(docstrings[i])): - line = docstrings[i][j] - if line: # non-blank line - if not see_also_line and see_also_re.search(line): - see_also_line = True # line begins with "See also" - elif see_also_line: # blank line following see also section - see_also_line = False # end see also section - - if see_also_line: - for n, o in entities_table.items(): - if isinstance(o, MatClass): - role = "class" - elif isinstance(o, MatFunction): - role = "func" - else: - role = None - if role: - nn = n.replace("+", "") # remove + from name - # escape . and add negative look-behind for ` - pat = "(? Date: Fri, 28 Apr 2023 12:53:51 -0500 Subject: [PATCH 05/31] Exclude auto-linking class name in " Properties:" or " Methods:". These are headings in class docstrings that are recognized by MATLAB's `help` and `doc`. --- sphinxcontrib/mat_documenters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 48573a5..f9ec7b6 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -213,6 +213,9 @@ def auto_link(self, docstrings): nn = n.replace("+", "") # remove + from name pat = nn.replace(".", "\.") # escape . in pattern pat = "(? Date: Fri, 28 Apr 2023 17:22:11 -0500 Subject: [PATCH 06/31] Fix issue with matching things that are not whole words. --- sphinxcontrib/mat_documenters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index f9ec7b6..3ac12ad 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -201,7 +201,7 @@ def auto_link(self, docstrings): if role in ["class", "func"]: nn = n.replace("+", "") # remove + from name # escape . and add negative look-behind for ` - pat = "(? Date: Mon, 1 May 2023 15:25:05 -0500 Subject: [PATCH 07/31] Allow case-insensitive match of "See Also" w or w/o trailing ":" --- sphinxcontrib/mat_documenters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 3ac12ad..db03367 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -184,7 +184,7 @@ def add_content(self, more_content, no_docstring=False): def auto_link(self, docstrings): # autolink known names in See also if self.env.config.matlab_auto_link == "see_also": - see_also_re = re.compile("See also\s+(.+)") + see_also_re = re.compile(r"See also:?", re.IGNORECASE) see_also_line = False for i in range(len(docstrings)): for j in range(len(docstrings[i])): From 99a59ba88f2fa5ab40fbc8493ca436884f5b5063 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Mon, 1 May 2023 15:26:21 -0500 Subject: [PATCH 08/31] Add tests for matlab_auto_link = "see_also" --- tests/roots/test_autodoc/BaseClass.m | 3 ++ tests/roots/test_autodoc/baseFunction.m | 5 +++ .../roots/test_autodoc/target/ClassExample.m | 2 + tests/test_autodoc.py | 42 +++++++++++++++++-- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/tests/roots/test_autodoc/BaseClass.m b/tests/roots/test_autodoc/BaseClass.m index a2b8ba5..9b2588a 100644 --- a/tests/roots/test_autodoc/BaseClass.m +++ b/tests/roots/test_autodoc/BaseClass.m @@ -1,5 +1,8 @@ classdef BaseClass % A class in the very root of the directory +% +% See Also +% ClassExample, baseFunction methods function obj = BaseClass(obj,args) diff --git a/tests/roots/test_autodoc/baseFunction.m b/tests/roots/test_autodoc/baseFunction.m index d1fa784..4de39f3 100644 --- a/tests/roots/test_autodoc/baseFunction.m +++ b/tests/roots/test_autodoc/baseFunction.m @@ -1,3 +1,8 @@ function y = baseFunction(x) % Return the base of x +% +% See Also: +% ClassMeow +% package.ClassBar + y = x; diff --git a/tests/roots/test_autodoc/target/ClassExample.m b/tests/roots/test_autodoc/target/ClassExample.m index 0632b33..26ced42 100644 --- a/tests/roots/test_autodoc/target/ClassExample.m +++ b/tests/roots/test_autodoc/target/ClassExample.m @@ -4,6 +4,8 @@ % :param a: first property of :class:`ClassExample` % :param b: second property of :class:`ClassExample` % :param c: third property of :class:`ClassExample` + % + % See also BaseClass, baseFunction. properties a % a property diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index ffc980a..9912538 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -34,7 +34,7 @@ def test_target(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" ) @@ -50,7 +50,21 @@ def test_target_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target_auto_link_see_also(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + app = make_app(srcdir=srcdir, confoverrides={"matlab_auto_link": "see_also"}) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + see_also_line = content[0][2][1][3] # a bit fragile, I know + assert len(content) == 1 + assert ( + see_also_line.rawsource == "See also :class:`BaseClass`, :func:`baseFunction`." ) @@ -138,7 +152,7 @@ def test_root(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\nClassExample, baseFunction\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\nClassMeow\npackage.ClassBar" ) @@ -154,7 +168,27 @@ def test_root_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\nClassExample, baseFunction\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\nClassMeow\npackage.ClassBar" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_root_auto_link_see_also(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + app = make_app(srcdir=srcdir, confoverrides={"matlab_auto_link": "see_also"}) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) + see_also_line_1 = content[0][2][1][1][0] # a bit fragile, I know + see_also_line_2 = content[0][4][1][1][0] # a bit fragile, I know + assert len(content) == 1 + assert ( + see_also_line_1.rawsource + == "See Also\n:class:`ClassExample`, :func:`baseFunction`\n\n" + ) + assert ( + see_also_line_2.rawsource + == "See Also:\n:class:`ClassMeow`\n:class:`package.ClassBar`" ) From 2cb1bed5abe7429146d26d666103243e7627221e Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Thu, 4 May 2023 14:34:29 -0500 Subject: [PATCH 09/31] Add code to force matlab_short_links to True if matlab_auto_link is not None. --- sphinxcontrib/matlab.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sphinxcontrib/matlab.py b/sphinxcontrib/matlab.py index d0da000..e72036e 100644 --- a/sphinxcontrib/matlab.py +++ b/sphinxcontrib/matlab.py @@ -845,6 +845,11 @@ def analyze(app): def ensure_configuration(app, env): + if env.matlab_auto_link: + logger.info( + f"[sphinxcontrib-matlabdomain] matlab_auto_link='{env.matlab_auto_link}', forcing matlab_short_links=True." + ) + env.matlab_short_links = True if env.matlab_short_links: logger.info( "[sphinxcontrib-matlabdomain] matlab_short_links=True, forcing matlab_keep_package_prefix=False." From b954cb11a198270bec63401ca4096ddf1c44281f Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Thu, 4 May 2023 16:15:55 -0500 Subject: [PATCH 10/31] Fix issue with broken auto-links if matlab_short_links is False Add negative look-ahead for ` or . or + Avoids turning ... :class:`target.myClass` ... into... :class:`target.:class:`myClass`` etc. --- sphinxcontrib/mat_documenters.py | 16 +++++++++------- tests/roots/test_autodoc/BaseClass.m | 2 +- tests/roots/test_autodoc/baseFunction.m | 4 ++-- tests/test_autodoc.py | 8 ++++---- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index db03367..613bbb9 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -200,8 +200,11 @@ def auto_link(self, docstrings): role = o.ref_role() if role in ["class", "func"]: nn = n.replace("+", "") # remove + from name - # escape . and add negative look-behind for ` - pat = r"(? Date: Thu, 4 May 2023 16:16:19 -0500 Subject: [PATCH 11/31] Revert "Add code to force matlab_short_links to True if matlab_auto_link is not None." This reverts commit 91746429a84548289b5eec41a9713b4511c1a1d4. --- sphinxcontrib/matlab.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sphinxcontrib/matlab.py b/sphinxcontrib/matlab.py index e72036e..d0da000 100644 --- a/sphinxcontrib/matlab.py +++ b/sphinxcontrib/matlab.py @@ -845,11 +845,6 @@ def analyze(app): def ensure_configuration(app, env): - if env.matlab_auto_link: - logger.info( - f"[sphinxcontrib-matlabdomain] matlab_auto_link='{env.matlab_auto_link}', forcing matlab_short_links=True." - ) - env.matlab_short_links = True if env.matlab_short_links: logger.info( "[sphinxcontrib-matlabdomain] matlab_short_links=True, forcing matlab_keep_package_prefix=False." From b0771910e090c38292cc34be15ccb37c3b6b3303 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 12 May 2023 17:47:51 -0500 Subject: [PATCH 12/31] Refactor "see also" auto-linking. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Look for each entity on “see also” line in the table, rather than the other way around. Adds double-backquoting of unknown entities. --- sphinxcontrib/mat_documenters.py | 67 +++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 613bbb9..c587d90 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -184,29 +184,51 @@ def add_content(self, more_content, no_docstring=False): def auto_link(self, docstrings): # autolink known names in See also if self.env.config.matlab_auto_link == "see_also": - see_also_re = re.compile(r"See also:?", re.IGNORECASE) - see_also_line = False + see_also_re = re.compile(r"(See also:?\s*)(\b.*\b)(.*)", re.IGNORECASE) + see_also_cond_re = re.compile(r"(\s*)(\b.*\b)(.*)") + is_see_also_line = False for i in range(len(docstrings)): for j in range(len(docstrings[i])): line = docstrings[i][j] if line: # non-blank line - if not see_also_line and see_also_re.search(line): - see_also_line = True # line begins with "See also" - elif see_also_line: # blank line following see also section - see_also_line = False # end see also section - - if see_also_line: - for n, o in entities_table.items(): - role = o.ref_role() - if role in ["class", "func"]: - nn = n.replace("+", "") # remove + from name - pat = ( - r"(? Date: Wed, 17 May 2023 17:26:04 -0500 Subject: [PATCH 13/31] Update tests to include MATLAB names in See also lines. --- tests/roots/test_autodoc/BaseClass.m | 2 +- tests/roots/test_autodoc/baseFunction.m | 2 ++ tests/test_autodoc.py | 31 +++++++++++++------------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/roots/test_autodoc/BaseClass.m b/tests/roots/test_autodoc/BaseClass.m index f966588..b33c7e8 100644 --- a/tests/roots/test_autodoc/BaseClass.m +++ b/tests/roots/test_autodoc/BaseClass.m @@ -2,7 +2,7 @@ % A class in the very root of the directory % % See Also -% target.ClassExample, baseFunction +% target.ClassExample, baseFunction, ClassExample methods function obj = BaseClass(obj,args) diff --git a/tests/roots/test_autodoc/baseFunction.m b/tests/roots/test_autodoc/baseFunction.m index 87b4349..4b9fa81 100644 --- a/tests/roots/test_autodoc/baseFunction.m +++ b/tests/roots/test_autodoc/baseFunction.m @@ -4,5 +4,7 @@ % See Also: % target.submodule.ClassMeow % target.package.ClassBar +% ClassMeow +% package.ClassBar y = x; diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c189180..d9362ba 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -41,9 +41,8 @@ def test_target(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_target_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - app = make_app( - srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} - ) + confdict = {"matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) @@ -57,7 +56,8 @@ def test_target_show_default_value(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_target_auto_link_see_also(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - app = make_app(srcdir=srcdir, confoverrides={"matlab_auto_link": "see_also"}) + confdict = {"matlab_auto_link": "see_also"} + app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) @@ -99,9 +99,8 @@ def test_package(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_package_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - app = make_app( - srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} - ) + confdict = {"matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) @@ -129,6 +128,8 @@ def test_submodule(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_submodule_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_auto_link": "see_also"} + app = make_app(srcdir=srcdir, confoverrides=confdict) app = make_app( srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} ) @@ -152,30 +153,30 @@ def test_root(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" ) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_root_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - app = make_app( - srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} - ) + confdict = {"matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" ) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_root_auto_link_see_also(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - app = make_app(srcdir=srcdir, confoverrides={"matlab_auto_link": "see_also"}) + confdict = {"matlab_auto_link": "see_also"} + app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) @@ -184,11 +185,11 @@ def test_root_auto_link_see_also(make_app, rootdir): assert len(content) == 1 assert ( see_also_line_1.rawsource - == "See Also\n:class:`target.ClassExample`, :func:`baseFunction`\n\n" + == "See Also\n:class:`target.ClassExample`, :func:`baseFunction`, :class:`ClassExample`\n\n" ) assert ( see_also_line_2.rawsource - == "See Also:\n:class:`target.submodule.ClassMeow`\n:class:`target.package.ClassBar`" + == "See Also:\n:class:`target.submodule.ClassMeow`\n:class:`target.package.ClassBar`\n:class:`ClassMeow`\n:class:`package.ClassBar`" ) From be95645bb95200923772ccb8bc4aaec703547b04 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 17 May 2023 17:43:21 -0500 Subject: [PATCH 14/31] Add test of double-backquotes around unknown entities in see also sections. --- tests/roots/test_autodoc/target/ClassExample.m | 2 +- tests/test_autodoc.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/roots/test_autodoc/target/ClassExample.m b/tests/roots/test_autodoc/target/ClassExample.m index 26ced42..93067b4 100644 --- a/tests/roots/test_autodoc/target/ClassExample.m +++ b/tests/roots/test_autodoc/target/ClassExample.m @@ -5,7 +5,7 @@ % :param b: second property of :class:`ClassExample` % :param c: third property of :class:`ClassExample` % - % See also BaseClass, baseFunction. + % See also BaseClass, baseFunction, unknownEntity. properties a % a property diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index d9362ba..1844433 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -34,7 +34,7 @@ def test_target(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" ) @@ -49,7 +49,7 @@ def test_target_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" ) @@ -64,7 +64,8 @@ def test_target_auto_link_see_also(make_app, rootdir): see_also_line = content[0][2][1][3] # a bit fragile, I know assert len(content) == 1 assert ( - see_also_line.rawsource == "See also :class:`BaseClass`, :func:`baseFunction`." + see_also_line.rawsource + == "See also :class:`BaseClass`, :func:`baseFunction`, ``unknownEntity``." ) From b35886392485b71a6244aff9fc4a459a8f57e2a2 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Thu, 18 May 2023 16:04:39 -0500 Subject: [PATCH 15/31] Create entities_name_map only once at the same time as entities_table. --- sphinxcontrib/mat_documenters.py | 9 +-------- sphinxcontrib/mat_types.py | 8 ++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index c587d90..98413ba 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -20,6 +20,7 @@ MatModuleAnalyzer, MatApplication, entities_table, + entities_name_map, strip_package_prefix, ) @@ -197,7 +198,6 @@ def auto_link(self, docstrings): elif match := see_also_re.search(line): is_see_also_line = True # line begins with "See also" entries_str = match.group(2) # the entries - entities_name_map = create_entities_name_map(entities_table) elif is_see_also_line: # blank line following see also section is_see_also_line = False # end see also section @@ -837,13 +837,6 @@ def document_members(self, all_members=False): pass -def create_entities_name_map(entities_table): - entities_name_map = {} - for n, o in entities_table.items(): - entities_name_map[strip_package_prefix(n)] = n - return entities_name_map - - def make_baseclass_links(env, obj): """Returns list of base class links""" obj_bases = obj.__bases__ diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index 2df135e..911e25e 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -51,6 +51,12 @@ # Will result in a short name of: package.ClassBar entities_table = {} +# Dictionary containing a map of names WITHOUT '+' in package names to +# the corresponding names WITH '+' in the package name. This is only +# used if "matlab_auto_link" is on AND "matlab_keep_package_prefix" +# is True AND a docstring with "see also" is encountered. +entities_name_map = {} + def shortest_name(dotted_path): # Creates the shortest valid MATLAB name from a dotted path @@ -108,6 +114,7 @@ def populate_entities_table(obj, path=""): fullpath = path + "." + o.name fullpath = fullpath.lstrip(".") entities_table[fullpath] = o + entities_name_map[strip_package_prefix(fullpath)] = fullpath if isinstance(o, MatModule): if o.entities: populate_entities_table(o, fullpath) @@ -152,6 +159,7 @@ def analyze(app): short_name = shortest_name(name) if short_name != name: short_names[short_name] = entity + entities_name_map[short_name] = short_name entities_table.update(short_names) From feb7594202d0872d40e97ade0888738103f1613a Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Thu, 18 May 2023 17:14:59 -0500 Subject: [PATCH 16/31] Modify logic so that matlab_auto_link = "all" handles see_also line first. ... including wrapping unknown names in double-backquotes. --- sphinxcontrib/mat_documenters.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 98413ba..7c372c2 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -184,7 +184,10 @@ def add_content(self, more_content, no_docstring=False): def auto_link(self, docstrings): # autolink known names in See also - if self.env.config.matlab_auto_link == "see_also": + if ( + self.env.config.matlab_auto_link == "see_also" + or self.env.config.matlab_auto_link == "all" + ): see_also_re = re.compile(r"(See also:?\s*)(\b.*\b)(.*)", re.IGNORECASE) see_also_cond_re = re.compile(r"(\s*)(\b.*\b)(.*)") is_see_also_line = False @@ -230,8 +233,8 @@ def auto_link(self, docstrings): match.group(1) + ", ".join(entries) + match.group(3) ) - # replace everywhere - elif self.env.config.matlab_auto_link == "all": + # auto-link everywhere + if self.env.config.matlab_auto_link == "all": for n, o in entities_table.items(): role = o.ref_role() if role in ["class", "func"]: From 8fb4b391f33d0f98c018b805cb0ec4f25d2a5056 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Tue, 23 May 2023 13:54:29 -0500 Subject: [PATCH 17/31] Refactor MatlabDocumenter.auto_link() into separate methods - auto_link_see_also() - auto_link_all() --- sphinxcontrib/mat_documenters.py | 127 +++++++++++++++++-------------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 7c372c2..b35ad3a 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -182,74 +182,83 @@ def add_content(self, more_content, no_docstring=False): for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) + def auto_link_see_also(self, docstrings): + # autolink known names in See also + see_also_re = re.compile(r"(See also:?\s*)(\b.*\b)(.*)", re.IGNORECASE) + see_also_cond_re = re.compile(r"(\s*)(\b.*\b)(.*)") + is_see_also_line = False + for i in range(len(docstrings)): + for j in range(len(docstrings[i])): + line = docstrings[i][j] + if line: # non-blank line + if is_see_also_line: + # find name + match = see_also_cond_re.search(line) + entries_str = match.group(2) # the entries + elif match := see_also_re.search(line): + is_see_also_line = True # line begins with "See also" + entries_str = match.group(2) # the entries + elif is_see_also_line: # blank line following see also section + is_see_also_line = False # end see also section + + if is_see_also_line and entries_str: + # split on , + entries = re.split(r"\s*,\s*", entries_str) + for k in range(len(entries)): + if entries[k].endswith("`"): + continue + + if ( + self.env.config.matlab_keep_package_prefix + and entries[k] in entities_table + ): + o = entities_table[entries[k]] + elif ( + not self.env.config.matlab_keep_package_prefix + and entries[k] in entities_name_map + ): + o = entities_table[entities_name_map[entries[k]]] + else: + o = None + if o: + role = o.ref_role() + if role in ["class", "func"]: + entries[k] = f":{role}:`{entries[k]}`" + else: + entries[k] = f"``{entries[k]}``" + docstrings[i][j] = ( + match.group(1) + ", ".join(entries) + match.group(3) + ) + return docstrings + + def auto_link_all(self, docstrings): + # auto-link everywhere + for n, o in entities_table.items(): + role = o.ref_role() + if role in ["class", "func"]: + nn = n.replace("+", "") # remove + from name + pat = ( + r"(? Date: Tue, 23 May 2023 17:58:42 -0500 Subject: [PATCH 18/31] Refactor make_baseclass_links() into 2 new MatClass methods - fullname() - link() --- sphinxcontrib/mat_documenters.py | 16 +------------- sphinxcontrib/mat_types.py | 36 ++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index b35ad3a..d7641bc 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -859,21 +859,7 @@ def make_baseclass_links(env, obj): if not entity: links.append(":class:`%s`" % base_class_name) else: - modname = entity.__module__ - classname = entity.name - if not env.config.matlab_keep_package_prefix: - modname = strip_package_prefix(modname) - - if env.config.matlab_short_links: - # modname is only used for package names - # - "target.+package" => "package" - # - "target" => "" - parts = modname.split(".") - parts = [part for part in parts if part.startswith("+")] - modname = ".".join(parts) - - link_name = f"{modname}.{classname}" - links.append(f":class:`{base_class_name}<{link_name}>`") + links.append(entity.link(env, base_class_name)) return links diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index 911e25e..fa97bc4 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -200,7 +200,7 @@ def __init__(self, name): self.name = name def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "ref" @property @@ -443,7 +443,7 @@ def __init__(self, name, path, package): self.entities = [] def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "mod" def safe_getmembers(self): @@ -859,7 +859,7 @@ def __init__(self, name, modname, tokens): self.rem_tks = tks # save extra tokens def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "func" @property @@ -1327,9 +1327,33 @@ def __init__(self, name, modname, tokens): self.rem_tks = idx # index of last token def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "class" + def fullname(self, env): + """Returns full name for class object, for use as link target""" + modname = self.__module__ + classname = self.name + if not env.config.matlab_keep_package_prefix: + modname = strip_package_prefix(modname) + + if env.config.matlab_short_links: + # modname is only used for package names + # - "target.+package" => "package" + # - "target" => "" + parts = modname.split(".") + parts = [part for part in parts if part.startswith("+")] + modname = ".".join(parts) + + return f"{modname}.{classname}" + + def link(self, env, name): + """Returns link for class object""" + if not name: + name = self.name + target = self.fullname(env) + return f":class:`{name}<{target}>`" + def attributes(self, idx, attr_types): """ Retrieve MATLAB class, property and method attributes. @@ -1485,7 +1509,7 @@ def __init__(self, name, cls, attrs): # self.class = attrs['class'] def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "attr" @property @@ -1501,7 +1525,7 @@ def __init__(self, modname, tks, cls, attrs): self.attrs = attrs def ref_role(self): - # role to use for references to this object (e.g. when generating auto-links) + """Returns role to use for references to this object (e.g. when generating auto-links)""" return "meth" def skip_tokens(self): From b21d7b4c07b1a8766f21b487f24a73b79f36c82e Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Tue, 23 May 2023 18:01:48 -0500 Subject: [PATCH 19/31] Change "see_also" value of matlab_auto_link to "basic". --- README.rst | 4 ++-- sphinxcontrib/mat_documenters.py | 2 +- tests/test_autodoc.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index e04a2bf..1eed79d 100644 --- a/README.rst +++ b/README.rst @@ -99,9 +99,9 @@ If you want the closest to MATLAB documentation style, use ``matlab_short_links ``matlab_auto_link`` Automatically convert the names of known classes and functions to links using - the ``:class:`` and ``:func:`` roles, respectively. Valid values are ``"see_also"`` + the ``:class:`` and ``:func:`` roles, respectively. Valid values are ``"basic"`` and ``"all"``. - * ``"see_also"`` - applies only to docstring lines that begin with "See also", + * ``"basic"`` - applies only to docstring lines that begin with "See also", and any subsequent lines before the next blank line. * ``"all"`` - applies to all docstring lines. diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index d7641bc..7c7ec69 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -251,7 +251,7 @@ def auto_link_all(self, docstrings): def auto_link(self, docstrings): # autolink known names in See also if ( - self.env.config.matlab_auto_link == "see_also" + self.env.config.matlab_auto_link == "basic" or self.env.config.matlab_auto_link == "all" ): docstrings = self.auto_link_see_also(docstrings) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 1844433..93f4b83 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -56,7 +56,7 @@ def test_target_show_default_value(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_target_auto_link_see_also(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - confdict = {"matlab_auto_link": "see_also"} + confdict = {"matlab_auto_link": "basic"} app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() @@ -129,7 +129,7 @@ def test_submodule(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_submodule_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - confdict = {"matlab_auto_link": "see_also"} + confdict = {"matlab_auto_link": "basic"} app = make_app(srcdir=srcdir, confoverrides=confdict) app = make_app( srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} @@ -176,7 +176,7 @@ def test_root_show_default_value(make_app, rootdir): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_root_auto_link_see_also(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - confdict = {"matlab_auto_link": "see_also"} + confdict = {"matlab_auto_link": "basic"} app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() From 5581ef714201d770301ed5206534066d66636eef Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Tue, 23 May 2023 18:05:13 -0500 Subject: [PATCH 20/31] Add auto-linking of property and method names in class docstring. Only affects names in lines following " Properties:" or " Methods:" --- sphinxcontrib/mat_documenters.py | 54 ++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 7c7ec69..75fc1f6 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -182,6 +182,9 @@ def add_content(self, more_content, no_docstring=False): for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) + def auto_link_basic(self, docstrings): + return self.auto_link_see_also(docstrings) + def auto_link_see_also(self, docstrings): # autolink known names in See also see_also_re = re.compile(r"(See also:?\s*)(\b.*\b)(.*)", re.IGNORECASE) @@ -249,12 +252,9 @@ def auto_link_all(self, docstrings): return docstrings def auto_link(self, docstrings): - # autolink known names in See also - if ( - self.env.config.matlab_auto_link == "basic" - or self.env.config.matlab_auto_link == "all" - ): - docstrings = self.auto_link_see_also(docstrings) + # basic auto-linking + if self.env.config.matlab_auto_link: # "basic" or "all" (i.e. not None) + docstrings = self.auto_link_basic(docstrings) # auto-link everywhere if self.env.config.matlab_auto_link == "all": @@ -995,6 +995,48 @@ def add_content(self, more_content, no_docstring=False): else: MatModuleLevelDocumenter.add_content(self, more_content) + def auto_link_basic(self, docstrings): + docstrings = MatlabDocumenter.auto_link_basic(self, docstrings) + return self.auto_link_class_members(docstrings) + + def auto_link_class_members(self, docstrings): + # auto link property and method names in class docstring + prop_re = re.compile(r"(.* Properties:)", re.IGNORECASE) + meth_re = re.compile(r"(.* Methods:)", re.IGNORECASE) + is_prop_line = False + is_meth_line = False + for i in range(len(docstrings)): + for j in range(len(docstrings[i])): + line = docstrings[i][j] + if line: # non-blank line + if prop_re.search(line): # line ends with "Properties:" + is_prop_line = True + is_meth_line = False + elif meth_re.search(line): # line ends with "Methods:" + is_prop_line = False + is_meth_line = True + elif is_prop_line: + # auto-link first word to corresponding property, if it exists + docstrings[i][j] = self.link_member("attr", line) + elif is_meth_line: + # auto-link first word to corresponding method, if it exists + docstrings[i][j] = self.link_member("meth", line) + elif is_prop_line: # blank line following properties section + is_prop_line = False # end properties section + elif is_meth_line: # blank line following methods section + is_meth_line = False # end methods section + + return docstrings + + def link_member(self, type, line): + p = re.compile(r"((\*\s*)?(\b\w*\b))(?=\s*-)") + if match := p.search(line): + name = match.group(3) + line = p.sub( + f"* :{type}:`{name} <{self.object.fullname(self.env)}.{name}>`", line, 1 + ) + return line + def document_members(self, all_members=False): if self.doc_as_attr: return From 715ae2781f57b9f239bd4136066eb8bab46a9733 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 24 May 2023 09:59:32 -0500 Subject: [PATCH 21/31] Remove leading . in MatClass.fullname() results --- sphinxcontrib/mat_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index fa97bc4..a5b7339 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -1345,7 +1345,7 @@ def fullname(self, env): parts = [part for part in parts if part.startswith("+")] modname = ".".join(parts) - return f"{modname}.{classname}" + return f"{modname}.{classname}".lstrip(".") def link(self, env, name): """Returns link for class object""" From 73526a306fd215209754e177703f19fb4c1af512 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 24 May 2023 10:00:43 -0500 Subject: [PATCH 22/31] Fix issue with matlab_auto_link = "all" trying to link class names within existing links. --- sphinxcontrib/mat_documenters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 75fc1f6..18fb9d7 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -241,7 +241,7 @@ def auto_link_all(self, docstrings): if role in ["class", "func"]: nn = n.replace("+", "") # remove + from name pat = ( - r"(? Date: Wed, 24 May 2023 10:01:24 -0500 Subject: [PATCH 23/31] Update tests for matlab_auto_link = "basic" to include property/method names in class docstrings. --- tests/roots/test_autodoc/BaseClass.m | 5 ++ .../roots/test_autodoc/target/ClassExample.m | 10 ++-- tests/test_autodoc.py | 49 ++++++++++++++++--- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/tests/roots/test_autodoc/BaseClass.m b/tests/roots/test_autodoc/BaseClass.m index b33c7e8..b434107 100644 --- a/tests/roots/test_autodoc/BaseClass.m +++ b/tests/roots/test_autodoc/BaseClass.m @@ -1,6 +1,11 @@ classdef BaseClass % A class in the very root of the directory % +% BaseClass Methods: +% BaseClass - the constructor, whose description extends +% to the next line +% DoBase - another BaseClass method +% % See Also % target.ClassExample, baseFunction, ClassExample diff --git a/tests/roots/test_autodoc/target/ClassExample.m b/tests/roots/test_autodoc/target/ClassExample.m index 93067b4..28d2637 100644 --- a/tests/roots/test_autodoc/target/ClassExample.m +++ b/tests/roots/test_autodoc/target/ClassExample.m @@ -1,9 +1,13 @@ classdef ClassExample < handle % Example class % - % :param a: first property of :class:`ClassExample` - % :param b: second property of :class:`ClassExample` - % :param c: third property of :class:`ClassExample` + % ClassExample Properties: + % a - first property of ClassExample + % b - second property of ClassExample + % c - third property of ClassExample + % ClassExample Methods: + % ClassExample - the constructor + % mymethod - a method in ClassExample % % See also BaseClass, baseFunction, unknownEntity. diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 93f4b83..f4827e9 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -31,10 +31,20 @@ def test_target(make_app, rootdir): app.builder.build_all() content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + ) + assert ( + property_section.rawsource + == "ClassExample Properties:\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\nClassExample - the constructor\nmymethod - a method in ClassExample\n" ) @@ -46,23 +56,43 @@ def test_target_show_default_value(make_app, rootdir): app.builder.build_all() content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nParameters\n\na – first property of ClassExample\n\nb – second property of ClassExample\n\nc – third property of ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + ) + assert ( + property_section.rawsource + == "ClassExample Properties:\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\nClassExample - the constructor\nmymethod - a method in ClassExample\n" ) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") -def test_target_auto_link_see_also(make_app, rootdir): +def test_target_auto_link_basic(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" confdict = {"matlab_auto_link": "basic"} app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know see_also_line = content[0][2][1][3] # a bit fragile, I know assert len(content) == 1 + assert ( + property_section.rawsource + == "ClassExample Properties:\n* :attr:`a ` - first property of ClassExample\n* :attr:`b ` - second property of ClassExample\n* :attr:`c ` - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\n* :meth:`ClassExample ` - the constructor\n* :meth:`mymethod ` - a method in ClassExample\n" + ) assert ( see_also_line.rawsource == "See also :class:`BaseClass`, :func:`baseFunction`, ``unknownEntity``." @@ -154,7 +184,7 @@ def test_root(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nBaseClass Methods:\n\nBaseClass - the constructor, whose description extends\n\nto the next line\n\nDoBase - another BaseClass method\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" ) @@ -169,21 +199,26 @@ def test_root_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nBaseClass Methods:\n\nBaseClass - the constructor, whose description extends\n\nto the next line\n\nDoBase - another BaseClass method\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" ) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") -def test_root_auto_link_see_also(make_app, rootdir): +def test_root_auto_link_basic(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" confdict = {"matlab_auto_link": "basic"} app = make_app(srcdir=srcdir, confoverrides=confdict) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) - see_also_line_1 = content[0][2][1][1][0] # a bit fragile, I know + method_section = content[0][2][1][1][0] # a bit fragile, I know + see_also_line_1 = content[0][2][1][1][1] # a bit fragile, I know see_also_line_2 = content[0][4][1][1][0] # a bit fragile, I know assert len(content) == 1 + assert ( + method_section.rawsource + == "BaseClass Methods:\n* :meth:`BaseClass ` - the constructor, whose description extends\n to the next line\n* :meth:`DoBase ` - another BaseClass method\n" + ) assert ( see_also_line_1.rawsource == "See Also\n:class:`target.ClassExample`, :func:`baseFunction`, :class:`ClassExample`\n\n" From 35aff050fedae7bae7f46677820640aeb84a87c7 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 24 May 2023 10:22:27 -0500 Subject: [PATCH 24/31] Add a test for matlab_auto_link = "all" --- tests/test_autodoc.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index f4827e9..a568c37 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -99,6 +99,32 @@ def test_target_auto_link_basic(make_app, rootdir): ) +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target_auto_link_all(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_auto_link": "all"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know + see_also_line = content[0][2][1][3] # a bit fragile, I know + assert len(content) == 1 + assert ( + property_section.rawsource + == "ClassExample Properties:\n* :attr:`a ` - first property of :class:`ClassExample`\n* :attr:`b ` - second property of :class:`ClassExample`\n* :attr:`c ` - third property of :class:`ClassExample`" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\n* :meth:`ClassExample ` - the constructor\n* :meth:`mymethod ` - a method in :class:`ClassExample`\n" + ) + assert ( + see_also_line.rawsource + == "See also :class:`BaseClass`, :func:`baseFunction`, ``unknownEntity``." + ) + + @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_classfolder(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" From 52591d531077f1edb3373bbdae4516a9eaf99a8f Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 24 May 2023 16:36:46 -0500 Subject: [PATCH 25/31] Update README.rst for latest auto-link behavior. --- README.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 1eed79d..59569f3 100644 --- a/README.rst +++ b/README.rst @@ -98,13 +98,18 @@ If you want the closest to MATLAB documentation style, use ``matlab_short_links = True`` in your ``conf.py`` file. ``matlab_auto_link`` - Automatically convert the names of known classes and functions to links using - the ``:class:`` and ``:func:`` roles, respectively. Valid values are ``"basic"`` + Automatically convert the names of known entities (e.g. classes, functions, + properties, methods) to links Valid values are ``"basic"`` and ``"all"``. - * ``"basic"`` - applies only to docstring lines that begin with "See also", - and any subsequent lines before the next blank line. - - * ``"all"`` - applies to all docstring lines. + * ``"basic"`` - Auto-links (1) known classes or functions that appear + in docstring lines that begin with "See also" and any subsequent + lines before the next blank line (unknown names are wrapped in + double-backquotes), and (2) property and method names that appear in + lists under " Properties:" and " Methods:" headings + in class docstrings. + + * ``"all"`` - Auto-links everything included with ``"basic"``, plus all + known classes and functions everywhere else they appear in any docstring. Default is ``None``. *Added in Version 0.20.0*. From 155e85d749985246883d94b5b9296416985048b9 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 10:22:46 -0500 Subject: [PATCH 26/31] Add parens following the name of a linked method. --- sphinxcontrib/mat_documenters.py | 10 ++++++++-- sphinxcontrib/mat_types.py | 2 +- tests/test_autodoc.py | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 18fb9d7..904fa48 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -235,7 +235,7 @@ def auto_link_see_also(self, docstrings): return docstrings def auto_link_all(self, docstrings): - # auto-link everywhere + # auto-link known classes and functions everywhere for n, o in entities_table.items(): role = o.ref_role() if role in ["class", "func"]: @@ -1029,11 +1029,17 @@ def auto_link_class_members(self, docstrings): return docstrings def link_member(self, type, line): + if type == "meth": + parens = "()" + else: + parens = "" p = re.compile(r"((\*\s*)?(\b\w*\b))(?=\s*-)") if match := p.search(line): name = match.group(3) line = p.sub( - f"* :{type}:`{name} <{self.object.fullname(self.env)}.{name}>`", line, 1 + f"* :{type}:`{name}{parens} <{self.object.fullname(self.env)}.{name}>`", + line, + 1, ) return line diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index a5b7339..ad85db9 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -1352,7 +1352,7 @@ def link(self, env, name): if not name: name = self.name target = self.fullname(env) - return f":class:`{name}<{target}>`" + return f":class:`{name} <{target}>`" def attributes(self, idx, attr_types): """ diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index a568c37..68aa326 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -91,7 +91,7 @@ def test_target_auto_link_basic(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\n* :meth:`ClassExample ` - the constructor\n* :meth:`mymethod ` - a method in ClassExample\n" + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor\n* :meth:`mymethod() ` - a method in ClassExample\n" ) assert ( see_also_line.rawsource @@ -117,7 +117,7 @@ def test_target_auto_link_all(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\n* :meth:`ClassExample ` - the constructor\n* :meth:`mymethod ` - a method in :class:`ClassExample`\n" + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor\n* :meth:`mymethod() ` - a method in :class:`ClassExample`\n" ) assert ( see_also_line.rawsource @@ -243,7 +243,7 @@ def test_root_auto_link_basic(make_app, rootdir): assert len(content) == 1 assert ( method_section.rawsource - == "BaseClass Methods:\n* :meth:`BaseClass ` - the constructor, whose description extends\n to the next line\n* :meth:`DoBase ` - another BaseClass method\n" + == "BaseClass Methods:\n* :meth:`BaseClass() ` - the constructor, whose description extends\n to the next line\n* :meth:`DoBase() ` - another BaseClass method\n" ) assert ( see_also_line_1.rawsource From b2281754c1e6f7797ac6d5706ed5b55c561766ea Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 14:32:01 -0500 Subject: [PATCH 27/31] Add auto-linking of method names in class/method/property docstrings. Requires matlab_auto_link = "all" and the method name must appear with a trailing () to be auto-linked. --- README.rst | 4 +- sphinxcontrib/mat_documenters.py | 26 ++++++++ .../test_autodoc/target/+package/ClassBar.m | 6 +- .../roots/test_autodoc/target/ClassExample.m | 2 +- tests/test_autodoc.py | 62 +++++++++++++++---- 5 files changed, 83 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 59569f3..553d1cb 100644 --- a/README.rst +++ b/README.rst @@ -109,7 +109,9 @@ If you want the closest to MATLAB documentation style, use ``matlab_short_links in class docstrings. * ``"all"`` - Auto-links everything included with ``"basic"``, plus all - known classes and functions everywhere else they appear in any docstring. + known classes and functions everywhere else they appear in any docstring, + and any names ending with "()" within class, property, or method docstrings + that match a method of the corresponding class. Default is ``None``. *Added in Version 0.20.0*. diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 904fa48..639e1eb 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -262,6 +262,20 @@ def auto_link(self, docstrings): return docstrings + def auto_link_methods(self, class_obj, docstrings): + for n, o in class_obj.methods.items(): + # negative look-behind for ` or . or <, then () + pat = r"(?`", + docstrings[i][j], + ) + + return docstrings + def get_object_members(self, want_all): """Return `(members_check_module, members)` where `members` is a list of `(membername, member)` pairs of the members of *self.object*. @@ -1043,6 +1057,10 @@ def link_member(self, type, line): ) return line + def auto_link_all(self, docstrings): + docstrings = MatlabDocumenter.auto_link_all(self, docstrings) + return self.auto_link_methods(self.object, docstrings) + def document_members(self, all_members=False): if self.doc_as_attr: return @@ -1204,6 +1222,10 @@ def format_args(self): def document_members(self, all_members=False): pass + def auto_link_all(self, docstrings): + docstrings = MatlabDocumenter.auto_link_all(self, docstrings) + return self.auto_link_methods(self.object.cls, docstrings) + class MatAttributeDocumenter(MatClassLevelDocumenter): """ @@ -1274,6 +1296,10 @@ def add_content(self, more_content, no_docstring=False): # no_docstring = True MatClassLevelDocumenter.add_content(self, more_content, no_docstring) + def auto_link_all(self, docstrings): + docstrings = MatlabDocumenter.auto_link_all(self, docstrings) + return self.auto_link_methods(self.object.cls, docstrings) + class MatInstanceAttributeDocumenter(MatAttributeDocumenter): """ diff --git a/tests/roots/test_autodoc/target/+package/ClassBar.m b/tests/roots/test_autodoc/target/+package/ClassBar.m index fec1e58..6161af9 100644 --- a/tests/roots/test_autodoc/target/+package/ClassBar.m +++ b/tests/roots/test_autodoc/target/+package/ClassBar.m @@ -1,10 +1,10 @@ classdef ClassBar < handle -% The Bar and Foo handler +% The Bar and Foo handler, with a doFoo() method. properties bars = 'bars' % Number of bars - % Number of foos + % Number of foos, used by doBar() method foos = 10 end @@ -20,7 +20,7 @@ function doFoo(obj) end function doBar(obj) - % Doing bar + % Doing bar, not called by ClassBar() end end end diff --git a/tests/roots/test_autodoc/target/ClassExample.m b/tests/roots/test_autodoc/target/ClassExample.m index 28d2637..c6c4eec 100644 --- a/tests/roots/test_autodoc/target/ClassExample.m +++ b/tests/roots/test_autodoc/target/ClassExample.m @@ -6,7 +6,7 @@ % b - second property of ClassExample % c - third property of ClassExample % ClassExample Methods: - % ClassExample - the constructor + % ClassExample - the constructor and a reference to mymethod() % mymethod - a method in ClassExample % % See also BaseClass, baseFunction, unknownEntity. diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 68aa326..5109ea3 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -36,7 +36,7 @@ def test_target(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" ) assert ( property_section.rawsource @@ -44,7 +44,7 @@ def test_target(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\nClassExample - the constructor\nmymethod - a method in ClassExample\n" + == "ClassExample Methods:\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n" ) @@ -61,7 +61,7 @@ def test_target_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + == "target\n\n\n\nclass target.ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" ) assert ( property_section.rawsource @@ -69,7 +69,7 @@ def test_target_show_default_value(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\nClassExample - the constructor\nmymethod - a method in ClassExample\n" + == "ClassExample Methods:\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n" ) @@ -91,7 +91,7 @@ def test_target_auto_link_basic(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor\n* :meth:`mymethod() ` - a method in ClassExample\n" + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor and a reference to mymethod()\n* :meth:`mymethod() ` - a method in ClassExample\n" ) assert ( see_also_line.rawsource @@ -117,7 +117,7 @@ def test_target_auto_link_all(make_app, rootdir): ) assert ( method_section.rawsource - == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor\n* :meth:`mymethod() ` - a method in :class:`ClassExample`\n" + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor and a reference to :meth:`mymethod() `\n* :meth:`mymethod() ` - a method in :class:`ClassExample`\n" ) assert ( see_also_line.rawsource @@ -146,11 +146,17 @@ def test_package(make_app, rootdir): app.builder.build_all() content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) + docstring1 = content[0][2][1][1] # a bit fragile, I know + docstring2 = content[0][2][1][2][0][1][1][4][1][0] # a bit fragile, I know + docstring3 = content[0][2][1][2][0][2][1][2][1][0] # a bit fragile, I know assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" ) + assert docstring1.rawsource == "The Bar and Foo handler, with a doFoo() method." + assert docstring2.rawsource == "Number of foos, used by doBar() method" + assert docstring3.rawsource == "Doing bar, not called by ClassBar()" @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") @@ -164,7 +170,37 @@ def test_package_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_package_auto_link_all(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_auto_link": "all"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) + docstring1 = content[0][2][1][1] # a bit fragile, I know + docstring2 = content[0][2][1][2][0][1][1][4][1][0] # a bit fragile, I know + docstring3 = content[0][2][1][2][0][2][1][2][1][0] # a bit fragile, I know + assert len(content) == 1 + assert ( + content[0].astext() + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + ) + assert ( + docstring1.rawsource + == "The Bar and Foo handler, with a :meth:`doFoo() ` method." + ) + assert ( + docstring2.rawsource + == "Number of foos, used by :meth:`doBar() ` method" + ) + assert ( + docstring3.rawsource + == "Doing bar, not called by :meth:`ClassBar() `" ) @@ -175,21 +211,23 @@ def test_submodule(make_app, rootdir): app.builder.build_all() content = pickle.loads((app.doctreedir / "index_submodule.doctree").read_bytes()) + bases_line = content[0][2][1][0] assert len(content) == 1 assert ( content[0].astext() == "submodule\n\n\n\nclass target.submodule.ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\ntarget.submodule.funcMeow(input)\n\nTests a function with comments after docstring" ) + assert ( + bases_line.rawsource + == "Bases: :class:`package.ClassBar `" + ) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_submodule_show_default_value(make_app, rootdir): srcdir = rootdir / "roots" / "test_autodoc" - confdict = {"matlab_auto_link": "basic"} + confdict = {"matlab_show_property_default_value": True} app = make_app(srcdir=srcdir, confoverrides=confdict) - app = make_app( - srcdir=srcdir, confoverrides={"matlab_show_property_default_value": True} - ) app.builder.build_all() content = pickle.loads((app.doctreedir / "index_submodule.doctree").read_bytes()) From 308c9a1b6eb9993c5e96d4ca6229b3616c1bcadf Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 14:34:41 -0500 Subject: [PATCH 28/31] Add autodoc tests for matlab_short_links = True. --- tests/test_autodoc_short_links.py | 299 ++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 tests/test_autodoc_short_links.py diff --git a/tests/test_autodoc_short_links.py b/tests/test_autodoc_short_links.py new file mode 100644 index 0000000..d138128 --- /dev/null +++ b/tests/test_autodoc_short_links.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- +""" + test_autodoc + ~~~~~~~~~~~~ + + Test the autodoc extension. + + :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +import pickle +import os +import sys + +import pytest + +from sphinx import addnodes +from sphinx.testing.fixtures import make_app, test_params # noqa: F811; +from sphinx.testing.path import path + + +@pytest.fixture(scope="module") +def rootdir(): + return path(os.path.dirname(__file__)).abspath() + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know + assert len(content) == 1 + assert ( + content[0].astext() + == "target\n\n\n\nclass ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb\n\na property with default value\n\n\n\nc\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + ) + assert ( + property_section.rawsource + == "ClassExample Properties:\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target_show_default_value(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know + assert len(content) == 1 + assert ( + content[0].astext() + == "target\n\n\n\nclass ClassExample(a)\n\nBases: handle\n\nExample class\n\nClassExample Properties:\n\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample\n\nClassExample Methods:\n\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n\nSee also BaseClass, baseFunction, unknownEntity.\n\nProperty Summary\n\n\n\n\n\na\n\na property\n\n\n\nb = 10\n\na property with default value\n\n\n\nc = [10; ... 30]\n\na property with multiline default value\n\nMethod Summary\n\n\n\n\n\nmymethod(b)\n\nA method in ClassExample\n\nParameters\n\nb – an input to mymethod()" + ) + assert ( + property_section.rawsource + == "ClassExample Properties:\na - first property of ClassExample\nb - second property of ClassExample\nc - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\nClassExample - the constructor and a reference to mymethod()\nmymethod - a method in ClassExample\n" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target_auto_link_basic(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_auto_link": "basic"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know + see_also_line = content[0][2][1][3] # a bit fragile, I know + assert len(content) == 1 + assert ( + property_section.rawsource + == "ClassExample Properties:\n* :attr:`a ` - first property of ClassExample\n* :attr:`b ` - second property of ClassExample\n* :attr:`c ` - third property of ClassExample" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor and a reference to mymethod()\n* :meth:`mymethod() ` - a method in ClassExample\n" + ) + assert ( + see_also_line.rawsource + == "See also :class:`BaseClass`, :func:`baseFunction`, ``unknownEntity``." + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_target_auto_link_all(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_auto_link": "all"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_target.doctree").read_bytes()) + property_section = content[0][2][1][2][0] # a bit fragile, I know + method_section = content[0][2][1][2][1] # a bit fragile, I know + see_also_line = content[0][2][1][3] # a bit fragile, I know + assert len(content) == 1 + assert ( + property_section.rawsource + == "ClassExample Properties:\n* :attr:`a ` - first property of :class:`ClassExample`\n* :attr:`b ` - second property of :class:`ClassExample`\n* :attr:`c ` - third property of :class:`ClassExample`" + ) + assert ( + method_section.rawsource + == "ClassExample Methods:\n* :meth:`ClassExample() ` - the constructor and a reference to :meth:`mymethod() `\n* :meth:`mymethod() ` - a method in :class:`ClassExample`\n" + ) + assert ( + see_also_line.rawsource + == "See also :class:`BaseClass`, :func:`baseFunction`, ``unknownEntity``." + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_classfolder(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_classfolder.doctree").read_bytes()) + assert len(content) == 1 + assert ( + content[0].astext() + == "classfolder\n\n\n\nclass ClassFolder(p)\n\nA class in a folder\n\nProperty Summary\n\n\n\n\n\np\n\na property of a class folder\n\nMethod Summary\n\n\n\n\n\nmethod_inside_classdef(a, b)\n\nMethod inside class definition" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_package(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) + docstring1 = content[0][2][1][1] # a bit fragile, I know + docstring2 = content[0][2][1][2][0][1][1][4][1][0] # a bit fragile, I know + docstring3 = content[0][2][1][2][0][2][1][2][1][0] # a bit fragile, I know + assert len(content) == 1 + assert ( + content[0].astext() + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + ) + assert docstring1.rawsource == "The Bar and Foo handler, with a doFoo() method." + assert docstring2.rawsource == "Number of foos, used by doBar() method" + assert docstring3.rawsource == "Doing bar, not called by ClassBar()" + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_package_show_default_value(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) + assert len(content) == 1 + assert ( + content[0].astext() + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_package_auto_link_all(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_auto_link": "all"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_package.doctree").read_bytes()) + docstring1 = content[0][2][1][1] # a bit fragile, I know + docstring2 = content[0][2][1][2][0][1][1][4][1][0] # a bit fragile, I know + docstring3 = content[0][2][1][2][0][2][1][2][1][0] # a bit fragile, I know + assert len(content) == 1 + assert ( + content[0].astext() + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + ) + assert ( + docstring1.rawsource + == "The Bar and Foo handler, with a :meth:`doFoo() ` method." + ) + assert ( + docstring2.rawsource + == "Number of foos, used by :meth:`doBar() ` method" + ) + assert ( + docstring3.rawsource + == "Doing bar, not called by :meth:`ClassBar() `" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_submodule(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_submodule.doctree").read_bytes()) + bases_line = content[0][2][1][0] + assert len(content) == 1 + assert ( + content[0].astext() + == "submodule\n\n\n\nclass ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\nfuncMeow(input)\n\nTests a function with comments after docstring" + ) + assert bases_line.rawsource == "Bases: :class:`package.ClassBar `" + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_submodule_show_default_value(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_submodule.doctree").read_bytes()) + assert len(content) == 1 + assert ( + content[0].astext() + == "submodule\n\n\n\nclass ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\nfuncMeow(input)\n\nTests a function with comments after docstring" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_root(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) + assert len(content) == 1 + assert ( + content[0].astext() + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nBaseClass Methods:\n\nBaseClass - the constructor, whose description extends\n\nto the next line\n\nDoBase - another BaseClass method\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_root_show_default_value(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_show_property_default_value": True} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) + assert len(content) == 1 + assert ( + content[0].astext() + == "root\n\n\n\nclass BaseClass(args)\n\nA class in the very root of the directory\n\nBaseClass Methods:\n\nBaseClass - the constructor, whose description extends\n\nto the next line\n\nDoBase - another BaseClass method\n\nSee Also\n\ntarget.ClassExample, baseFunction, ClassExample\n\nConstructor Summary\n\n\n\n\n\nBaseClass(args)\n\nThe constructor\n\nMethod Summary\n\n\n\n\n\nDoBase()\n\nDo the Base thing\n\n\n\nbaseFunction(x)\n\nReturn the base of x\n\nSee Also:\n\ntarget.submodule.ClassMeow\ntarget.package.ClassBar\nClassMeow\npackage.ClassBar" + ) + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") +def test_root_auto_link_basic(make_app, rootdir): + srcdir = rootdir / "roots" / "test_autodoc" + confdict = {"matlab_short_links": True, "matlab_auto_link": "basic"} + app = make_app(srcdir=srcdir, confoverrides=confdict) + app.builder.build_all() + + content = pickle.loads((app.doctreedir / "index_root.doctree").read_bytes()) + method_section = content[0][2][1][1][0] # a bit fragile, I know + see_also_line_1 = content[0][2][1][1][1] # a bit fragile, I know + see_also_line_2 = content[0][4][1][1][0] # a bit fragile, I know + assert len(content) == 1 + assert ( + method_section.rawsource + == "BaseClass Methods:\n* :meth:`BaseClass() ` - the constructor, whose description extends\n to the next line\n* :meth:`DoBase() ` - another BaseClass method\n" + ) + assert ( + see_also_line_1.rawsource + == "See Also\n:class:`target.ClassExample`, :func:`baseFunction`, :class:`ClassExample`\n\n" + ) + assert ( + see_also_line_2.rawsource + == "See Also:\n:class:`target.submodule.ClassMeow`\n:class:`target.package.ClassBar`\n:class:`ClassMeow`\n:class:`package.ClassBar`" + ) + + +if __name__ == "__main__": + pytest.main([__file__]) From 0dd551485c15dda3c8f829635645d76e70c19377 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 14:37:22 -0500 Subject: [PATCH 29/31] Fix bug in class links when matlab_short_links = True This affected both base class links and auto-linking. The issue was that package names were not being included in the link target. --- sphinxcontrib/mat_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index ad85db9..4fa425b 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -1334,9 +1334,6 @@ def fullname(self, env): """Returns full name for class object, for use as link target""" modname = self.__module__ classname = self.name - if not env.config.matlab_keep_package_prefix: - modname = strip_package_prefix(modname) - if env.config.matlab_short_links: # modname is only used for package names # - "target.+package" => "package" @@ -1345,6 +1342,9 @@ def fullname(self, env): parts = [part for part in parts if part.startswith("+")] modname = ".".join(parts) + if not env.config.matlab_keep_package_prefix: + modname = strip_package_prefix(modname) + return f"{modname}.{classname}".lstrip(".") def link(self, env, name): From bf48ec849dfd6a3e76825043fe9effc9bbf97273 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 15:26:05 -0500 Subject: [PATCH 30/31] Fix class links so names are consistent with targets. This makes the visible names consistent with the `matlab_short_links` and `matlab_keep_package_prefix` options. --- sphinxcontrib/mat_documenters.py | 2 +- sphinxcontrib/mat_types.py | 9 +++++---- tests/test_autodoc.py | 9 +++------ tests/test_autodoc_short_links.py | 2 +- tests/test_duplicated_link.py | 2 +- tests/test_package_links.py | 2 +- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index 639e1eb..ffac23a 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -873,7 +873,7 @@ def make_baseclass_links(env, obj): if not entity: links.append(":class:`%s`" % base_class_name) else: - links.append(entity.link(env, base_class_name)) + links.append(entity.link(env)) return links diff --git a/sphinxcontrib/mat_types.py b/sphinxcontrib/mat_types.py index 4fa425b..4908dde 100644 --- a/sphinxcontrib/mat_types.py +++ b/sphinxcontrib/mat_types.py @@ -1347,12 +1347,13 @@ def fullname(self, env): return f"{modname}.{classname}".lstrip(".") - def link(self, env, name): + def link(self, env, name=None): """Returns link for class object""" - if not name: - name = self.name target = self.fullname(env) - return f":class:`{name} <{target}>`" + if name: + return f":class:`{name} <{target}>`" + else: + return f":class:`{target}`" def attributes(self, idx, attr_types): """ diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 5109ea3..55b5e17 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -215,12 +215,9 @@ def test_submodule(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "submodule\n\n\n\nclass target.submodule.ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\ntarget.submodule.funcMeow(input)\n\nTests a function with comments after docstring" - ) - assert ( - bases_line.rawsource - == "Bases: :class:`package.ClassBar `" + == "submodule\n\n\n\nclass target.submodule.ClassMeow\n\nBases: target.package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\ntarget.submodule.funcMeow(input)\n\nTests a function with comments after docstring" ) + assert bases_line.rawsource == "Bases: :class:`target.package.ClassBar`" @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") @@ -234,7 +231,7 @@ def test_submodule_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "submodule\n\n\n\nclass target.submodule.ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\ntarget.submodule.funcMeow(input)\n\nTests a function with comments after docstring" + == "submodule\n\n\n\nclass target.submodule.ClassMeow\n\nBases: target.package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\ntarget.submodule.funcMeow(input)\n\nTests a function with comments after docstring" ) diff --git a/tests/test_autodoc_short_links.py b/tests/test_autodoc_short_links.py index d138128..6b5fbb1 100644 --- a/tests/test_autodoc_short_links.py +++ b/tests/test_autodoc_short_links.py @@ -221,7 +221,7 @@ def test_submodule(make_app, rootdir): content[0].astext() == "submodule\n\n\n\nclass ClassMeow\n\nBases: package.ClassBar\n\nClass which inherits from a package\n\nMethod Summary\n\n\n\n\n\nsay()\n\nSay Meow\n\n\n\nfuncMeow(input)\n\nTests a function with comments after docstring" ) - assert bases_line.rawsource == "Bases: :class:`package.ClassBar `" + assert bases_line.rawsource == "Bases: :class:`package.ClassBar`" @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") diff --git a/tests/test_duplicated_link.py b/tests/test_duplicated_link.py index e05dc7c..79b71e4 100644 --- a/tests/test_duplicated_link.py +++ b/tests/test_duplicated_link.py @@ -39,7 +39,7 @@ def test_with_prefix(make_app, rootdir): assert ( section.astext() == "NiceFiniteGroup\n\n\n\nclass +replab.NiceFiniteGroup\n\nBases: " - "replab.FiniteGroup\n\nA nice finite group is a finite group equipped " + "+replab.FiniteGroup\n\nA nice finite group is a finite group equipped " "with an injective homomorphism into a permutation group\n\nReference that triggers the error: eqv" ) diff --git a/tests/test_package_links.py b/tests/test_package_links.py index d4c895f..63105b2 100644 --- a/tests/test_package_links.py +++ b/tests/test_package_links.py @@ -37,7 +37,7 @@ def test_with_prefix(make_app, rootdir): assert ( content[5].astext() - == "class +replab.Action\n\nBases: replab.Str\n\nAn action group …\n\nMethod Summary\n\n\n\n\n\nleftAction(g, p)\n\nReturns the left action" + == "class +replab.Action\n\nBases: +replab.Str\n\nAn action group …\n\nMethod Summary\n\n\n\n\n\nleftAction(g, p)\n\nReturns the left action" ) From fcb1f0c3144a9a133e696504ba884696b5b06ed5 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Fri, 26 May 2023 18:51:55 -0500 Subject: [PATCH 31/31] Skip over literal blocks when auto-linking with matlab_auto_link = "all" --- sphinxcontrib/mat_documenters.py | 39 ++++++++++++++++--- .../test_autodoc/target/+package/funcFoo.m | 5 +++ tests/test_autodoc.py | 6 +-- tests/test_autodoc_short_links.py | 6 +-- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/sphinxcontrib/mat_documenters.py b/sphinxcontrib/mat_documenters.py index ffac23a..dcc130e 100644 --- a/sphinxcontrib/mat_documenters.py +++ b/sphinxcontrib/mat_documenters.py @@ -246,9 +246,25 @@ def auto_link_all(self, docstrings): + r"\b(?!(`|\sProperties|\sMethods):)" # negative look-ahead for ` or " Properties:" or " Methods:" ) p = re.compile(pat) + no_link = 0 # normal mode (no literal block detected) for i in range(len(docstrings)): for j in range(len(docstrings[i])): - docstrings[i][j] = p.sub(f":{role}:`{nn}`", docstrings[i][j]) + # skip over literal blocks (i.e. line ending with ::, blank line, indented line) + if docstrings[i][j].endswith("::"): + no_link = -1 # 1st sign of literal block + elif not docstrings[i][j]: # blank line + if no_link == -1: # if 1st sign already detected + no_link = -2 # 2nd sign of literal block + elif no_link == 1: # if in literal block + no_link = 0 # end the literal block, restart linking + elif no_link == -2 and docstrings[i][j].startswith(" "): + # indented line after 1st 2 signs + no_link = 1 # beginning of literal block (stop linking!) + elif no_link != 1: # not in a literal block, go ahead and link + docstrings[i][j] = p.sub( + f":{role}:`{nn}`", docstrings[i][j] + ) + return docstrings def auto_link(self, docstrings): @@ -267,12 +283,25 @@ def auto_link_methods(self, class_obj, docstrings): # negative look-behind for ` or . or <, then () pat = r"(?`", - docstrings[i][j], - ) + # skip over literal blocks (i.e. line ending with ::, blank line, indented line) + if docstrings[i][j].endswith("::"): + no_link = -1 # 1st sign of literal block + elif not docstrings[i][j]: # blank line + if no_link == -1: # if 1st sign already detected + no_link = -2 # 2nd sign of literal block + elif no_link == 1: # if in literal block + no_link = 0 # end the literal block, start linking again + elif no_link == -2 and docstrings[i][j].startswith(" "): + # indented line after 1st 2 signs + no_link = 1 # beginning of literal block (stop linking!) + elif no_link != 1: # not in a literal block, go ahead and link + docstrings[i][j] = p.sub( + f":meth:`{n}() <{class_obj.fullname(self.env)}.{n}>`", + docstrings[i][j], + ) return docstrings diff --git a/tests/roots/test_autodoc/target/+package/funcFoo.m b/tests/roots/test_autodoc/target/+package/funcFoo.m index a49774d..435ccbf 100644 --- a/tests/roots/test_autodoc/target/+package/funcFoo.m +++ b/tests/roots/test_autodoc/target/+package/funcFoo.m @@ -1,5 +1,10 @@ function [x, y] = funcFoo(u, t) % Function that does Foo +% :: % +% x = package.funcFoo(u) +% [x, y] = package.funcFoo(u, t) +% +% Test for auto-linking with baseFunction and BaseClass, etc. x = u; y = t; diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 55b5e17..607b07b 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -152,7 +152,7 @@ def test_package(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction and BaseClass, etc." ) assert docstring1.rawsource == "The Bar and Foo handler, with a doFoo() method." assert docstring2.rawsource == "Number of foos, used by doBar() method" @@ -170,7 +170,7 @@ def test_package_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction and BaseClass, etc." ) @@ -188,7 +188,7 @@ def test_package_auto_link_all(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass target.package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\ntarget.package.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction() and BaseClass, etc." ) assert ( docstring1.rawsource diff --git a/tests/test_autodoc_short_links.py b/tests/test_autodoc_short_links.py index 6b5fbb1..f83af7c 100644 --- a/tests/test_autodoc_short_links.py +++ b/tests/test_autodoc_short_links.py @@ -155,7 +155,7 @@ def test_package(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction and BaseClass, etc." ) assert docstring1.rawsource == "The Bar and Foo handler, with a doFoo() method." assert docstring2.rawsource == "Number of foos, used by doBar() method" @@ -173,7 +173,7 @@ def test_package_show_default_value(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars = 'bars'\n\nNumber of bars\n\n\n\nfoos = 10\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction and BaseClass, etc." ) @@ -191,7 +191,7 @@ def test_package_auto_link_all(make_app, rootdir): assert len(content) == 1 assert ( content[0].astext() - == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo" + == "package\n\n\n\nclass package.ClassBar\n\nBases: handle\n\nThe Bar and Foo handler, with a doFoo() method.\n\nConstructor Summary\n\n\n\n\n\nClassBar()\n\nInitialize the bars and foos\n\nProperty Summary\n\n\n\n\n\nbars\n\nNumber of bars\n\n\n\nfoos\n\nNumber of foos, used by doBar() method\n\nMethod Summary\n\n\n\n\n\ndoBar()\n\nDoing bar, not called by ClassBar()\n\n\n\ndoFoo()\n\nDoing foo\n\n\n\n\n\n\n\npackage.funcFoo(u, t)\n\nFunction that does Foo\n\nx = package.funcFoo(u)\n[x, y] = package.funcFoo(u, t)\n\nTest for auto-linking with baseFunction() and BaseClass, etc." ) assert ( docstring1.rawsource