Skip to content

Commit

Permalink
Working on #94
Browse files Browse the repository at this point in the history
First solution is to monkey-patch docutils directly, which will make
doctest accept >> instead of >>>.

The solution interferes with Python doctest rendering, but matlabdomain
and python autodoc does not play well anyway.
  • Loading branch information
joeced committed Oct 12, 2019
1 parent 4169b6b commit 6fecf26
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 10 deletions.
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ In order for the Sphinx MATLAB domain to auto-document MATLAB source code, set
the config value of ``matlab_src_dir`` to the absolute path instead of adding
them to ``sys.path``. Currently only one MATLAB path can be specified, but all
subfolders in that tree will be searched.

The encoding of the matlab files can be specified using the config value of
``matlab_src_encoding``. By default, the files will be read as utf-8 and parsing
errors will be replaced using ? chars.

`Octave Doctest <https://github.com/catch22/octave-doctest>` is a feature
similar to `Python Doctest <https://docs.python.org/3.7/library/doctest.html>,
which are rendered as code if they are present in a documentation string. To
enable this feature set ``matlab_enable_doctest = True``. Anything prefixed
with ``>> `` will be rendered as code, as it is assumed to be a doctest.
For convenience the `primary domain <http://sphinx-doc.org/config.html#confval-primary_domain>`_
can be set to ``mat``.

Expand Down
12 changes: 11 additions & 1 deletion sphinxcontrib/matlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,11 +712,20 @@ def get_objects(self):
for refname, (docname, type) in self.data['objects'].items():
yield (refname, refname, type, docname, refname, 1)


def config_inited(app, config):
""" Monkey-patching to allow Octave style doctests"""
from docutils.parsers.rst import states
if config.matlab_enable_doctest:
states.Body.patterns["doctest"] = r'>>( +|$)'


def setup(app):
app.add_domain(MATLABDomain)
# autodoc
app.add_config_value('matlab_src_dir', None, 'env')
app.add_config_value('matlab_src_encoding', None, 'env')
app.add_config_value('matlab_enable_doctest', None, 'env')
app.add_autodocumenter(doc.MatModuleDocumenter)
app.add_autodoc_attrgetter(doc.MatModule, doc.MatModule.getter)
app.add_autodocumenter(doc.MatClassDocumenter)
Expand All @@ -727,4 +736,5 @@ def setup(app):
app.add_autodocumenter(doc.MatMethodDocumenter)
app.add_autodocumenter(doc.MatAttributeDocumenter)
app.add_autodocumenter(doc.MatInstanceAttributeDocumenter)
app.add_autodocumenter(doc.MatScriptDocumenter)
app.add_autodocumenter(doc.MatScriptDocumenter)
app.connect('config-inited', config_inited)
20 changes: 20 additions & 0 deletions tests/roots/test_doctests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = test_autodoc
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
12 changes: 12 additions & 0 deletions tests/roots/test_doctests/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os

matlab_src_dir = os.path.abspath('.')
matlab_enable_doctest = True

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

# The suffix of source filenames.
source_suffix = '.rst'

nitpicky = True
5 changes: 5 additions & 0 deletions tests/roots/test_doctests/contents.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

.. automodule:: target

.. autofunction:: f_with_doctests

36 changes: 36 additions & 0 deletions tests/roots/test_doctests/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=test_autodoc

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%

:end
popd
12 changes: 12 additions & 0 deletions tests/roots/test_doctests/target/f_with_doctests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function number = f_with_doctests()
% Returns the number
%
% Returns:
% number (numeric): The number
%
% Example:
% >> getMyNumber()
% 5

number = 5;
end
13 changes: 6 additions & 7 deletions tests/test_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ def rootdir():

def test_setup(make_app, rootdir):
srcdir = rootdir / 'roots' / 'test_autodoc'
app = make_app(srcdir=srcdir)
app = make_app(srcdir=srcdir,)
app.builder.build_all()

contents = app.env.get_doctree('contents')

content = pickle.loads((app.doctreedir / 'contents.doctree').bytes())

assert isinstance(content[3], addnodes.desc)
assert content[3][0].astext() == 'class target.ClassExamplea'
assert content[3][1].astext() == """Bases: handle
assert isinstance(contents[3], addnodes.desc)
assert contents[3][0].astext() == 'class target.ClassExamplea'
assert contents[3][1].astext() == """Bases: handle
Example class
Expand All @@ -60,6 +60,5 @@ def test_setup(make_app, rootdir):
# We still have warning regarding overriding auto...
# assert app._warning.getvalue() == ''


if __name__ == '__main__':
pytest.main([__file__])
45 changes: 45 additions & 0 deletions tests/test_doctests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import pickle
import os

import pytest

from docutils import nodes
from sphinx import addnodes
from sphinx import version_info
from sphinx.testing.fixtures import test_params, make_app
from sphinx.testing.path import path


@pytest.fixture(scope='module')
def reload_docutils():
""" As we monkey-patch 'Body' in the 'states' module, we need to reload it
to ensure that we can actually apply the change if it has already been
loaded in another test.
"""
import docutils.parsers.rst.states
import importlib
importlib.reload(docutils.parsers.rst.states)


@pytest.fixture(scope='module')
def rootdir(reload_docutils):
return path(os.path.dirname(__file__)).abspath()


def test_setup(make_app, rootdir):
srcdir = rootdir / 'roots' / 'test_doctests'
app = make_app(srcdir=srcdir, freshenv=True)
app.env.matlab_enable_doctest = True

app.builder.build_all()

contents = app.env.get_doctree('contents')
doctest_blocks = [node for node in contents.traverse(nodes.doctest_block)]
assert len(doctest_blocks) == 1
assert doctest_blocks[0].astext() == '>> getMyNumber()\n5'


if __name__ == '__main__':
pytest.main([__file__])
3 changes: 1 addition & 2 deletions tests/test_parse_mfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,7 @@ def test_f_with_function_variable():
obj = mat_types.MatObject.parse_mfile(mfile, 'f_with_function_variable', 'test_data')
assert obj.name == 'f_with_function_variable'
assert obj.retv == ['obj']
assert obj.args == ['the_functions', '~']
print(obj.docstring)
assert obj.args == ['the_functions', '~']


def test_ClassWithGetterSetter():
Expand Down

0 comments on commit 6fecf26

Please sign in to comment.