Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auto-link functions, classes and members in docstring text #178

Closed
rdzman opened this issue Apr 17, 2023 · 8 comments
Closed

auto-link functions, classes and members in docstring text #178

rdzman opened this issue Apr 17, 2023 · 8 comments

Comments

@rdzman
Copy link
Contributor

rdzman commented Apr 17, 2023

Summary

As part of the goal of supplying in-source documentation that looks good and works well with both Sphinx and MATLAB's help and doc commands (see #140), it would be ideal if links to functions, classes, properties, methods, etc. could be auto-generated.

That is, for example, if the name of any item that is being autodocumented is found anywhere in the docstring it would be turned into the appropriate link. E.g. Writing This uses a myPkg.myClass object to ... would be automatically converted to This uses a :class:`myPkg.myClass` object to ... and rendered by Sphinx as a link.

Background

Currently, MATLAB's doc (or help) command automatically does two things:

  1. Converts names to links in two contexts:
    (a) A line in the help text (docstring) that begins with See also. If there are multiple such lines, it only acts on the last one.
    (b) Property and methods names for MyClass listed under the "MyClass Properties:" and "MyClass Methods:" headings, repectively, in the class docstring.
  2. Highlights (bold/colored) the name of the current class, function, etc, without making it a link.

With Sphinx any linking or highlighting must be done manually (e.g. using :class:`MyClass` or even ``my_variable``). This creates 2 problems when displayed by MATLAB's doc or help.

Problems

  1. It breaks any auto-linking or highlighting mentioned above.
  2. It adds clutter by including these Sphinx roles in the documentation displayed by MATLAB.

Current Alternatives

So currently, I have to choose between (1) non-linked, non-highlighted names in the Sphinx generated docs, or (2) no auto-linking and lots of ugly Sphinx-role-clutter in the MATLAB display.

I don't want to have to pick one or the other, especially in the See also lines, where I'm currently duplicating the line to get links in both contexts.

Questions

I'd love to hear your thoughts on the overall idea. And here are some specific questions regarding the possibility of this package including new auto-linking functionality sometime in the not-too-distant future:

  1. Is there any possibility of at least creating auto-linking for See also lines?
  2. What about for the properties and methods in the class docstring described in 1(b) above?
  3. If those are possibilities, could we extend it to auto-linking everywhere in the docstring?
  4. Would it help if we limited the auto-linking to fully qualified class and function names, excluding property and method names?

... or ...

  1. Should I assume that links in Sphinx documentation will only ever be those created by using explicit roles in the source documentation? 😦
@rdzman
Copy link
Contributor Author

rdzman commented Apr 17, 2023

Below are some of screen shots based on an updated minimal example I'm working with (classdef-min-ex2.zip). The ZIP file includes HTML generated by Sphinx (in build/html) and MATLAB's doc, via the undocumented help2html (in build/matlab).

MATLAB help

Screenshot 2023-04-17 at 3 25 56 PM

MATLAB doc

Screenshot 2023-04-17 at 3 26 43 PM

Sphinx

Screenshot 2023-04-17 at 3 27 15 PM

@joeced
Copy link
Collaborator

joeced commented Apr 19, 2023

It's on my todo list as well, I thought of this for a long time.
As soon as I get #171 done, it will be easier to make these kind of features.

@rdzman
Copy link
Contributor Author

rdzman commented Apr 19, 2023

For the writing of my documentation, I'm currently leaning toward leaving out all explicit Sphinx roles, to avoid that clutter in the MATLAB doc and help output, with the hope that one day we can get this implemented. In the short-term my Sphinx docs won't have any special highlighting or linking of classes, functions, etc., but a simple re-building of the docs at a later date can hopefully add them. That way I won't have to go back and make changes to my documentation.

I'm also starting to look into adding this feature myself, beginning with just the See also lines. I just need to find how to look up the object from the name to decide what role to use, or whether to just use double back-ticks (in case it's not something we can link to ... e.g. a MATLAB built-in).

@rdzman
Copy link
Contributor Author

rdzman commented Apr 27, 2023

Here's a first crack at auto-linking known classes and functions in See also lines. That is, any line in any docstring that begins with See also will wrap known names with a :class: or :func: role. For example, if MyClass, mypkg.MySubClass, and my_function are known entities, then the lines ...

See also MyClass, mypkg.MySubClass,
my_function, some_other_function.

... are converted to ...

See also :class:`MyClass`, :class:`mypkg.MySubClass`,
:func:`my_function`, some_other_function.

I do this by adding the following line ...

docstrings = self.auto_link(docstrings)

... just before line 204 of the add_content() method of MatlabDocumenter.

# add content from docstrings
if not no_docstring:
docstrings = self.get_doc()
if not docstrings:
# append at least a dummy docstring, so that the event
# autodoc-process-docstring is fired and can add some
# content if desired
docstrings.append([])
for i, line in enumerate(self.process_doc(docstrings)):
self.add_line(line, sourcename, i)

And I define the new auto_link() method as follows.

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 = "(?<!`)" + nn.replace(".", "\.")
                        line = re.sub(pat, f":{role}:`{nn}`", line)
                docstrings[i][j] = line
    return docstrings

Note that this relies on the entities_table from #171.

Right now it only works on the fully qualified names of classes and functions.

I also tried doing the substitution on every line in docstrings, which I think we will eventually want to do. However, we will want to think this through carefully to avoid linking anything incorrectly (e.g. linking constructor name to class objects), or linking names in certain contexts, such as property and method section headings.

I think we will also want to generate auto links for properties and methods, but I wasn't sure where to find the corresponding objects as I don't see them in the top-level of entities_table.

I thought I'd wait until #171 was merged to create a PR for this, but I'd love to hear thoughts or feedback.

@joeced
Copy link
Collaborator

joeced commented Apr 27, 2023

Nice idea. I had the same line of thought regarding

I also tried doing the substitution on every line in docstrings, which I think we will eventually want to do.

I think you observation is correct, that one has to be careful not the link to everything in there.

Regarding #171, it's progressing quite fine. I got base-class linking to work! Now I just need to merge or rebase with master and finally get it into master.

@rdzman
Copy link
Contributor Author

rdzman commented Apr 28, 2023

Update - I've pushed my current work on this to the auto-link branch on my fork.

Current status:

  • Added a matlab_auto_link config option that currently takes 2 options, "see_also" and "all".
  • Added a ref_role() method to MatObject types to return the role to use for references to each type of object.
  • Need to update the regex to match only whole words. Right now if you have myClass and myClassFoo, it will replace myClassFoo with :class:`myClass`Foo, which isn't quite what we're looking for.

I'll rebase on master and create a PR as soon as #171 is merged.

@joeced
Copy link
Collaborator

joeced commented Sep 12, 2023

@rdzman I have had the version 0.20.0 out as release candidate for quiet a while. I'm looking into actually releasing it. Do you have any objections to just get out?

Then we could maybe close this issue?

@rdzman
Copy link
Contributor Author

rdzman commented Sep 12, 2023

Ship it!

And thanks for including this. For my use case, this and other updates you've included in recent months have improved this tool tremendously!

@rdzman rdzman closed this as completed Sep 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants