Skip to content

Commit

Permalink
Add configuration matlab_short_links (#171)
Browse files Browse the repository at this point in the history
* Added new configuration `matlab_short_links`

Working on getting more MATLAB like rendering and links.
Rather than `target.+package.ClassBar`, it will displayed and referenced
as `package.ClassBar`.

Major rewrite of parsing for MATLAB files in `matlab_src_dir`. It's now done
at startup and stored in a dict.

Added a lot of tests to verify the new functionality.
  • Loading branch information
joeced authored Apr 28, 2023
1 parent 7bcc353 commit 81f5f17
Show file tree
Hide file tree
Showing 39 changed files with 819 additions and 767 deletions.
26 changes: 26 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
sphinxcontrib-matlabdomain-0.19.0 (2023-MM-DD)
==============================================

* Added new configuration: ``matlab_short_links``. Finally, we are getting
closer to render documentation closer to how MathWorks does it. The parsing of
MATLAB functions, clasess, etc. was rewritten. We now parse all MATLAB files
in ``matlab_src_dir``. Further, you can now generate docs for files in the
root folder (something that only worked for packages before).
* We also updated the class rendering to be closer to that of MathWorks. Instead
of putting both properties and methods together, they are now grouped by:
*Constructor Summary*, *Property Summary* and *Method Summary*. Below is an
example of how the ``ClassBar`` from
``tests/roots/test_autodoc/target/+package/ClassBar.m`` was rendered before
and after.

Before

.. image:: docs/render_classes_0.18.0.png
:alt: Rendering of default ``ClassBar.m`` in 0.18.0

After

.. image:: docs/render_classes_0.19.0.png
:alt: Rendering of default ``ClassBar.m`` in 0.19.0


sphinxcontrib-matlabdomain-0.18.0 (2023-04-14)
==============================================

Expand Down
51 changes: 39 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,26 @@ The Python package must be installed with::
pip install -U sphinxcontrib-matlabdomain

In general, the usage is the same as for documenting Python code. The package
is tested with Python >= 3.8 and Sphinx >=4.0.0 and <6.0.0.
is tested with Python >= 3.8 and Sphinx >=4.0.0.

For a Python 2 compatible version the package must be installed with::

pip install sphinxcontrib-matlabdomain==0.11.8


Configuration
-------------
In your Sphinx ``conf.py`` file add ``sphinxcontrib.matlab`` to the list of
extensions. ::

extensions = ['sphinxcontrib.matlab', 'sphinx.ext.autodoc']

For convenience the `primary domain <https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-primary_domain>`_
can be set to ``mat`` with.::

primary_domain = "mat"


Additional Configuration
~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -57,22 +64,38 @@ Additional Configuration

``matlab_src_encoding``
The encoding of the MATLAB files. By default, the files will be read as utf-8
and parsing errors will be replaced using ? chars. *Added in Version 0.9.0.*
and parsing errors will be replaced using ? chars. *Added in Version 0.9.0*.

``matlab_keep_package_prefix``
Determines if the MATLAB package prefix ``+`` is displayed in the
generated documentation. Default is ``True``. When ``False``, packages are
generated documentation. Default is ``False``. When ``False``, packages are
still referred to in ReST using ``+pakage.+subpkg.func`` but the output
will be ``pakage.other.func()``. *Added in Version
0.11.0.*
0.11.0*.

``matlab_show_property_default_value``
Show property default values in the rendered document. Default is ``False``,
which is what MathWorks does in their documentation. *Added in Version
0.16.0.*
0.16.0*.

For convenience the `primary domain <https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-primary_domain>`_
can be set to ``mat``.
``matlab_short_links``
Shorten all class, package and functions to the minimum length. This assumes
that everything is in the path as we would expect it in MATLAB. This will
resemble a more MATLAB-like presentation. If it is ``True`` is forces
``matlab_keep_package_prefix = False``. Further, it allows for much shorter and cleaner references.
Example, given a path to a class like ``target.subfolder.ClassFoo``.
* With ``False``::

:class:`target.subfolder.ClassFoo`

* With ``True``::

:class:`ClassFoo`

Default is ``False``. *Added in Version 0.19.0*.

If you want the closest to MATLAB documentation style, use ``matlab_short_links
= True`` in your ``conf.py`` file.


Roles and Directives
Expand Down Expand Up @@ -193,6 +216,10 @@ Use the following to document::
:show-inheritance:
:members:

In version 0.19.0 the ``.. automodule::`` directive can also take a ``.`` as
argument, which allows you to document classes or functions in the root of
``matlab_src_dir``.


Module Index
------------
Expand Down Expand Up @@ -224,18 +251,18 @@ Instead use the ``mat:`` prefix before the desired directives::
Online Demo
===========

.. warning::

The online demo is highly outdated!

The test docs in the repository are online here:
http://bwanamarko.alwaysdata.net/matlabdomain/

.. note::

Sphinx style markup are used to document parameters, types, returns and
exceptions. There must be a blank comment line before and after the
parameter descriptions. Currently property docstrings are only collected if
they are on the same line following the property definition. Getter and
setter methods are documented like methods currently, but the dot is
replaced by an underscore. Default values for properties are represented as
unicode strings, therefore strings will be double quoted.
parameter descriptions.


Users
Expand Down
Binary file added docs/render_classes_0.18.0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/render_classes_0.19.0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 55 additions & 65 deletions sphinxcontrib/mat_documenters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
MatException,
MatModuleAnalyzer,
MatApplication,
modules,
entities_table,
strip_package_prefix,
)

import re
Expand Down Expand Up @@ -76,7 +77,9 @@ class MatlabDocumenter(PyDocumenter):
domain = "mat"

def parse_name(self):
"""Determine what module to import and what attribute to document.
"""
From: sphinx/ext/autodoc/__init__.py
Determine what module to import and what attribute to document.
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
*self.args* and *self.retann* if parsing and resolving was successful.
Expand Down Expand Up @@ -114,6 +117,8 @@ def parse_name(self):
self.fullname = (self.modname or "") + (
self.objpath and "." + ".".join(self.objpath) or ""
)
self.fullname = self.fullname.lstrip(".")

return True

def import_object(self):
Expand All @@ -122,34 +127,17 @@ def import_object(self):
Returns True if successful, False if an error occurred.
"""
# get config_value with absolute path to MATLAB source files
basedir = self.env.config.matlab_src_dir
MatObject.basedir = basedir # set MatObject base directory
MatObject.sphinx_env = self.env # pass env to MatObject cls
MatObject.sphinx_app = self.env.app # pass app to MatObject cls

# sets Matlab src file encoding for parsing
MatObject.encoding = self.env.config.matlab_src_encoding
if self.objpath:
logger.debug(
"[sphinxcontrib-matlabdomain] from %s import %s",
self.modname,
".".join(self.objpath),
)
try:
logger.debug("[sphinxcontrib-matlabdomain] import %s", self.modname)
MatObject.matlabify(self.modname)
parent = None
obj = self.module = modules[self.modname]
logger.debug("[sphinxcontrib-matlabdomain] => %r", obj)
for part in self.objpath:
parent = obj
logger.debug("[sphinxcontrib-matlabdomain] getattr(_, %r)", part)
obj = self.get_attr(obj, part)
logger.debug("[sphinxcontrib-matlabdomain] => %r", obj)
self.object_name = part
self.parent = parent
self.object = obj
msg = f"[sphinxcontrib-matlabdomain] MatlabDocumenter.import_object {self.modname=}, {self.objpath=}, {self.fullname=}."
logger.debug(msg)
if len(self.objpath) > 1:
lookup_name = ".".join([self.modname, self.objpath[0]])
lookup_name = lookup_name.lstrip(".")
obj = entities_table[lookup_name]
self.object = self.get_attr(obj, self.objpath[1])
else:
lookup_name = self.fullname.lstrip(".")
self.object = entities_table[lookup_name]
return True
# this used to only catch SyntaxError, ImportError and AttributeError,
# but importing modules with side effects can raise all kinds of errors
Expand All @@ -173,25 +161,7 @@ def import_object(self):

def add_content(self, more_content, no_docstring=False):
"""Add content from docstrings, attribute documentation and user."""
# set sourcename and add content from attribute documentation
if self.analyzer:
# prevent encoding errors when the file name is non-ASCII
if not isinstance(self.analyzer.srcname, str):
filename = str(self.analyzer.srcname)
else:
filename = self.analyzer.srcname
sourcename = "%s:docstring of %s" % (filename, self.fullname)

attr_docs = self.analyzer.find_attr_docs()
if self.objpath:
key = (".".join(self.objpath[:-1]), self.objpath[-1])
if key in attr_docs:
no_docstring = True
docstrings = [attr_docs[key]]
for i, line in enumerate(self.process_doc(docstrings)):
self.add_line(line, sourcename, i)
else:
sourcename = "docstring of %s" % self.fullname
sourcename = "docstring of %s" % self.fullname

# add content from docstrings
if not no_docstring:
Expand Down Expand Up @@ -642,7 +612,7 @@ def get_object_members(self, want_all):
# documenting imported objects
return True, self.object.safe_getmembers()
else:
memberlist = self.object.__all__
memberlist = [name for name, obj in self.object.__all__]
else:
memberlist = self.options.members or []
ret = []
Expand All @@ -667,6 +637,14 @@ class MatModuleLevelDocumenter(MatlabDocumenter):
"""
Specialized Documenter subclass for objects on module level (functions,
classes, data/constants).
From: sphinx/ext/autodoc/__init__.py
Resolve the module and name of the object to document given by the
arguments and the current module/class.
Must return a pair of the module name and a chain of attributes; for
example, it would return ``('zipfile', ['ZipFile', 'open'])`` for the
``zipfile.ZipFile.open`` method.
"""

def resolve_name(self, modname, parents, path, base):
Expand Down Expand Up @@ -788,21 +766,32 @@ def document_members(self, all_members=False):
pass


def make_baseclass_links(obj):
def make_baseclass_links(env, obj):
"""Returns list of base class links"""
obj_bases = obj.__bases__
links = []
if len(obj_bases):
base_classes = obj_bases.items()
for b, v in base_classes:
if not v:
links.append(":class:`%s`" % b)
for base_class_name, entity in base_classes:
if not entity:
links.append(":class:`%s`" % base_class_name)
else:
mod_name = v.__module__
if mod_name.startswith("+"):
links.append(":class:`+%s`" % b)
else:
links.append(":class:`%s.%s`" % (mod_name, b))
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}>`")

return links


Expand Down Expand Up @@ -852,7 +841,7 @@ def format_args(self):
if initmeth is None or not isinstance(initmeth, MatMethod):
return None
if initmeth.args:
if initmeth.args[0] == "obj":
if initmeth.args[0] in ("obj", "self"):
return "(" + ", ".join(initmeth.args[1:]) + ")"
else:
return "(" + ", ".join(initmeth.args) + ")"
Expand Down Expand Up @@ -884,7 +873,7 @@ def add_directive_header(self, sig):
# add inheritance info, if wanted
if not self.doc_as_attr and self.options.show_inheritance:
self.add_line("", "<autodoc>")
base_class_links = make_baseclass_links(self.object)
base_class_links = make_baseclass_links(self.env, self.object)
if base_class_links:
self.add_line(
_(" Bases: %s") % ", ".join(base_class_links), "<autodoc>"
Expand Down Expand Up @@ -1007,10 +996,11 @@ def document_members(self, all_members=False):
]

# container
self.add_line("", "<autodoc>")
self.add_line(".. container:: members", "<autodoc>")
self.add_line("", "<autodoc>")
self.indent += " "
if cons_names or prop_names or meth_names or other_names:
self.add_line("", "<autodoc>")
self.add_line(".. container:: members", "<autodoc>")
self.add_line("", "<autodoc>")
self.indent += " "

# constructor
if cons_names:
Expand Down Expand Up @@ -1089,7 +1079,7 @@ def import_object(self):

def format_args(self):
if self.object.args:
if self.object.args[0] == "obj":
if self.object.args[0] in ("obj", "self"):
return "(" + ", ".join(self.object.args[1:]) + ")"
else:
return "(" + ", ".join(self.object.args) + ")"
Expand Down
Loading

0 comments on commit 81f5f17

Please sign in to comment.