Skip to content

Commit

Permalink
Added support for documenting MATLAB application files (#97)
Browse files Browse the repository at this point in the history
- Sources the summary and description from the app metadata.
- Added an ``application`` directive and a ``app`` reference.
- Added example usage to tests/test_docs/index.rst
  • Loading branch information
ilent2 authored and joeced committed Oct 29, 2019
1 parent bdfdc35 commit 2a3edd9
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 8 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Contributors
* cleobis
* Hugo Leblanc
* ptitrex
* Isaac Lenton (ilent2)
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ Directive MATLAB object
``.. attribute:: attrname`` **property definition**
``.. autoattribute:: attrname`` * auto-document
``:attr:`attrname``` * reference
``.. application:: appname`` **application definition**
``.. autoapplication:: appname`` * auto-document
``:app:`appname``` * reference
==================================== ===========================================

Several options are available for auto-directives.
Expand Down
17 changes: 16 additions & 1 deletion sphinxcontrib/mat_documenters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from __future__ import unicode_literals

from .mat_types import (MatModule, MatObject, MatFunction, MatClass, MatProperty,
MatMethod, MatScript, MatException, MatModuleAnalyzer)
MatMethod, MatScript, MatException, MatModuleAnalyzer,
MatApplication)


import re
Expand Down Expand Up @@ -1028,3 +1029,17 @@ def can_document_member(cls, member, membername, isattr, parent):

def document_members(self, all_members=False):
pass

class MatApplicationDocumenter(MatModuleLevelDocumenter):
"""
Specialized Documenter subclass for Matlab Applications (.mlapp)
"""
objtype = 'application'

@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, MatApplication)

def document_members(self, all_members=False):
pass

94 changes: 90 additions & 4 deletions sphinxcontrib/mat_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
from sphinx.util import logging
from sphinxcontrib.mat_lexer import MatlabLexer
from pygments.token import Token
from zipfile import ZipFile
import xml.etree.ElementTree as ET

logger = logging.getLogger('matlab-domain')

MAT_DOM = 'sphinxcontrib-matlabdomain'
__all__ = ['MatObject', 'MatModule', 'MatFunction', 'MatClass', \
'MatProperty', 'MatMethod', 'MatScript', 'MatException', \
'MatModuleAnalyzer', 'MAT_DOM']
'MatModuleAnalyzer', 'MatApplication', 'MAT_DOM']

# TODO: use `self.tokens.pop()` instead of idx += 1, see MatFunction

Expand All @@ -43,11 +45,14 @@ class MatObject(object):
:param name: Name of MATLAB object.
:type name: str
MATLAB objects can be :class:`MatModule`, :class:`MatFunction` or
:class:`MatClass`. :class:`MatModule` are just folders that define a psuedo
namespace for :class:`MatFunction` and :class:`MatClass` in that folder.
MATLAB objects can be :class:`MatModule`, :class:`MatFunction`,
:class:`MatApplication` or :class:`MatClass`.
:class:`MatModule` are just folders that define a psuedo
namespace for :class:`MatFunction`, :class:`MatApplication`
and :class:`MatClass` in that folder.
:class:`MatFunction` and :class:`MatClass` must begin with either
``function`` or ``classdef`` keywords.
:class:`MatApplication` must be a ``.mlapp`` file.
"""
basedir = None
encoding = None
Expand Down Expand Up @@ -124,6 +129,11 @@ def matlabify(objname):
msg = '[%s] matlabify %s from\n\t%s.'
logger.debug(msg, MAT_DOM, package, mfile)
return MatObject.parse_mfile(mfile, name, path, MatObject.encoding) # parse mfile
elif os.path.isfile(fullpath + '.mlapp'):
mlappfile = fullpath + '.mlapp'
msg = '[%s] matlabify %s from\n\t%s.'
logger.debug(msg, MAT_DOM, package, mlappfile)
return MatObject.parse_mlappfile(mlappfile, name, path)
return None

@staticmethod
Expand Down Expand Up @@ -183,6 +193,53 @@ def isClass(token):
return MatScript(name, modname, tks)
return None

@staticmethod
def parse_mlappfile(mlappfile, name, path):
"""
Uses ZipFile to read the metadata/appMetadata.xml file and
the metadata/coreProperties.xml file description tags.
Parses XML content using ElementTree.
:param mlappfile: Full path of ``.mlapp`` file.
:type mlappfile: str
:param name: Name of :class:`MatApplication`.
:type name: str
:param path: Path of module containing :class:`MatApplication`.
:type path: str
:returns: :class:`MatApplication` representing the application.
"""

# TODO: We could use this method to parse other matlab binaries

# Read contents of meta-data file
# This might change in different Matlab versions
with ZipFile(mlappfile, 'r') as mlapp:
meta = ET.fromstring(mlapp.read('metadata/appMetadata.xml'))
core = ET.fromstring(mlapp.read('metadata/coreProperties.xml'))

metaNs = { 'ns' : "http://schemas.mathworks.com/appDesigner/app/2017/appMetadata" }
coreNs = {
'cp': "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
'dc': "http://purl.org/dc/elements/1.1/",
'dcmitype': "http://purl.org/dc/dcmitype/",
'dcterms': "http://purl.org/dc/terms/",
'xsi': "http://www.w3.org/2001/XMLSchema-instance"
}

coreDesc = core.find('dc:description', coreNs)
metaDesc = meta.find('ns:description', metaNs)

doc = []
if coreDesc is not None:
doc.append(coreDesc.text)
if metaDesc is not None:
doc.append(metaDesc.text)
docstring = '\n\n'.join(doc)

modname = path.replace(os.sep, '.') # module name

return MatApplication(name, modname, docstring)

@staticmethod
def _remove_comment_header(code):
"""
Expand Down Expand Up @@ -1191,6 +1248,35 @@ def __doc__(self):
def __module__(self):
return self.module


class MatApplication(MatObject):
"""
Representation of the documentation in a Matlab Application.
:param name: Name of :class:`MatObject`.
:type name: str
:param modname: Name of folder containing :class:`MatObject`.
:type modname: str
:param desc: Summary and description string.
:type desc: str
"""

def __init__(self, name, modname, desc):
super(MatApplication, self).__init__(name)
#: Path of folder containing :class:`MatApplication`.
self.module = modname
#: docstring
self.docstring = desc

@property
def __doc__(self):
return self.docstring

@property
def __module__(self):
return self.module


class MatException(MatObject):
def __init__(self, name, path, tks):
super(MatException, self).__init__(name)
Expand Down
18 changes: 15 additions & 3 deletions sphinxcontrib/matlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def after_content(self):

class MatModulelevel(MatObject):
"""
Description of an object on module level (functions, data).
Description of an object on module level (functions, data, application).
"""

def needs_arglist(self):
Expand All @@ -283,6 +283,10 @@ def get_index_text(self, modname, name_cls):
if not modname:
return _('%s (built-in variable)') % name_cls[0]
return _('%s (in module %s)') % (name_cls[0], modname)
elif self.objtype == 'application':
if not modname:
return _('%s (built-in application)') % name_cls[0]
return _('%s (in module %s)') % (name_cls[0], modname)
else:
return ''

Expand Down Expand Up @@ -610,6 +614,7 @@ class MATLABDomain(Domain):
'attribute': ObjType(_('attribute'), 'attr', 'obj'),
'module': ObjType(_('module'), 'mod', 'obj'),
'script': ObjType(_('script'), 'scpt', 'obj'),
'application': ObjType(_('application'), 'app', 'obj'),
}

directives = {
Expand All @@ -625,7 +630,8 @@ class MATLABDomain(Domain):
'currentmodule': MatCurrentModule,
'decorator': MatDecoratorFunction,
'decoratormethod': MatDecoratorMethod,
'script': MatModulelevel,
'script': MatModulelevel,
'application': MatModulelevel,
}
roles = {
'data': MatXRefRole(),
Expand All @@ -637,7 +643,8 @@ class MATLABDomain(Domain):
'meth': MatXRefRole(fix_parens=True),
'mod': MatXRefRole(),
'obj': MatXRefRole(),
'scpt': MatXRefRole(),
'scpt': MatXRefRole(),
'app': MatXRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, objtype
Expand Down Expand Up @@ -807,5 +814,10 @@ def setup(app):
'autoinstanceattribute',
mat_directives.MatlabAutodocDirective)

app.registry.add_documenter('mat:application', doc.MatApplicationDocumenter)
app.add_directive_to_domain('mat',
'autoapplication',
mat_directives.MatlabAutodocDirective)

app.add_autodoc_attrgetter(doc.MatModule, doc.MatModule.getter)
app.add_autodoc_attrgetter(doc.MatClass, doc.MatClass.getter)
Binary file added tests/test_data/Application.mlapp
Binary file not shown.
6 changes: 6 additions & 0 deletions tests/test_docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ A Matlab unittest class
:members:
:show-inheritance:

A matlab application (``.mlapp`` file)
++++++++++++++++++++++++++++++++++++++

.. autoapplication:: test_data.Application

This is a reference to an application :app:`test_data.Application`

Indices and tables
==================
Expand Down

0 comments on commit 2a3edd9

Please sign in to comment.