diff --git a/.gitignore b/.gitignore index eb1c8849a..4788806e8 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ config.log coverage/ coverage.info doc/sphinx/cython/examples -doc/sphinx/matlab/examples +doc/sphinx/matlab/examples/*.rst +doc/sphinx/matlab/tutorials/*.rst +doc/sphinx/matlab/code-docs/*.rst diff --git a/SConstruct b/SConstruct index 385cc473e..17d6a94a5 100644 --- a/SConstruct +++ b/SConstruct @@ -55,6 +55,8 @@ if 'clean' in COMMAND_LINE_TARGETS: removeFile('include/cantera/base/config.h') removeFile('ext/f2c_libs/arith.h') removeDirectory('doc/sphinx/matlab/examples') + removeDirectory('doc/sphinx/matlab/tutorials') + removeDirectory('doc/sphinx/matlab/code-docs') removeDirectory('doc/sphinx/cython/examples') for name in os.listdir('.'): if name.endswith('.msi'): diff --git a/doc/SConscript b/doc/SConscript index 3f80d9a94..a6fc29ed8 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -4,6 +4,70 @@ Import('env', 'build', 'install') localenv = env.Clone() +from collections import namedtuple +Page = namedtuple('Page', ['name', 'title', 'objects']) + +# Set up functions to pseudo-autodoc the MATLAB toolbox +def extract_matlab_docstring(mfile): + """ + Return the docstring from mfile, assuming that it consists of the + first uninterrupted comment block. + """ + docstring = ".. mat:function:: " + with open(mfile, 'r') as in_file: + # The function name is read from the first line + docstring += get_function_name(in_file.readline()) + '\n' + + # By convention, the second line (called H1 in the MATLAB documentation) + # is read by various MATLAB functions, so it should be in the format + # MATLAB expects - FUNCTIONNAME Summary. We read in this line and + # add the summary to the docstring. If the line doesn't match the + # format, just write it to the docstring as is. + line = in_file.readline() + try: + docstring += ' ' + line.split(' ')[1] + '\n' + except IndexError: + docstring += line + '\n' + + # Skip the next line, which is a duplicate of the first. It is here + # because MATLAB doesn't show the function definition in its help. + in_file.readline() + + # For the rest of the lines in the file, get the line if it is + # in the first unbroken comment section and add it to the docstring. + for line in in_file.readlines(): + try: + if line.lstrip().startswith('%'): + docstring += ' '*4 + line.lstrip()[2:-1] + '\n' + else: + break + except IndexError: + docstring += '\n' + + return docstring + '\n' + +def get_function_name(str): + """ + Return the function or classdef signature, assuming that + the string starts with either 'function ' or 'classdef '. + """ + if str.startswith('function '): + sig = str[len('function '):] + elif str.startswith('classdef '): + sig = str[len('classdef '):] + else: + print "Unknown function declaration in MATLAB document", str + + # Split the function signature on the equals sign, if it exists. + # We don't care about what comes before the equals sign, since + # if a function returns, the docs will tell us. If there is no + # =, return the whole signature. + if '=' in sig: + idx = sig.index('=') + return sig[idx+2:] + else: + return sig + if localenv['doxygen_docs']: if localenv['graphvizdir']: localenv.Append(PATH=localenv['graphvizdir']) @@ -36,14 +100,112 @@ if localenv['sphinx_docs']: build(b) localenv.Depends(sphinxdocs, b) + # Create a list of MATLAB classes to document. This uses the NamedTuple + # structure defined at the top of the file. The @Data and @Utilities + # classes are fake classes for the purposes of documentation only. Each + # Page represents one html page of the documentation. + pages = [ + Page('importing', 'Importing Phase Objects', + ['@Solution', '@Mixture',] + ), + Page('thermodynamics', 'Thermodynamic Properties', + ['@ThermoPhase'] + ), + Page('kinetics', 'Chemical Kinetics', ['@Kinetics']), + Page('transport', 'Transport Properties', ['@Transport']), + Page('zero-dim', 'Zero-Dimensional Reactor Networks', + ['@Func', '@Reactor', '@ReactorNet', '@FlowDevice', '@Wall'] + ), + Page('one-dim', 'One-Dimensional Reacting Flows', + ['1D/@Domain1D', '1D/@Stack'] + ), + Page('data', 'Built-In Thermochemical Data', + ['@Data'] + ), + Page('utilities', 'Utility Functions', + ['@Utilities', '@XML_Node'] + ), + Page('interface', 'Interfaces', ['@Interface']), + ] + + # Create a dictionary of extra files associated with each class. These + # files are listed relative to the top directory interfaces/matlab/cantera + extra = { + '@Solution': ['IdealGasMix.m', 'importPhase.m',], + '@Func': ['gaussian.m', 'polynom.m'], + '@Reactor': ['ConstPressureReactor.m', + 'FlowReactor.m', 'IdealGasConstPressureReactor.m', + 'IdealGasReactor.m', 'Reservoir.m'], + '@FlowDevice': ['MassFlowController.m', 'Valve.m'], + '1D/@Domain1D': ['1D/AxiStagnFlow.m', '1D/AxisymmetricFlow.m', + '1D/Inlet.m', '1D/Outlet.m', '1D/OutletRes.m', + '1D/Surface.m', '1D/SymmPlane.m'], + '1D/@Stack': ['1D/FreeFlame.m', '1D/npflame_init.m'], + '@Interface': ['importEdge.m', 'importInterface.m'], + '@Data': ['air.m', 'constants.m', 'gasconstant.m', 'GRI30.m', + 'Hydrogen.m', 'Methane.m', 'Nitrogen.m', 'oneatm.m', + 'Oxygen.m', 'Water.m'], + '@Utilities': ['adddir.m', 'ck2cti.m', 'cleanup.m', 'geterr.m',] + } + + # These files do not need to be documented in the MATLAB classes because they + # are generics that are overloaded per-class. Since the loop checks for these + # strings in each file name, hndl.m is the same as *hndl.m* (to use globbing + # notation). + nodoc_matlab_files = ['set.m', 'clear.m', 'display.m', 'hndl.m', 'private', 'subsref.m'] + + # Loop through the pages list to document each class + for page in pages: + tempenv = env.Clone() + + # Set the title header + title = page.title + tempenv['title'] = '='*len(title) + '\n' + title + '\n' + '='*len(title) + doc = '' + + # The base directory of the MATLAB toolbox relative to the sphinx build directory + base = '../interfaces/matlab/toolbox' + for obj in page.objects: + all_files = [] + # Set the subheader based on the class name + doc += obj.split('@')[1] + '\n' + '-'*len(obj.split('@')[1]) + '\n\n' + if os.path.isdir(pjoin(base,obj)): + class_files = os.listdir(pjoin(base,obj)) + class_files = [name for name in class_files if not any(x in name for x in nodoc_matlab_files)] + all_files = [class_files.pop(class_files.index(obj.split('@')[1]+'.m'))] + extra_files = extra.get(obj,[]) + all_files += sorted(class_files + extra_files) + class_files.insert(0,all_files[0]) + else: + all_files = extra.get(obj,[]) + for file in all_files: + if file in class_files: + doc += extract_matlab_docstring(os.path.relpath(pjoin(base,obj,file))) + else: + doc += extract_matlab_docstring(os.path.relpath(pjoin(base,file))) + + tempenv['matlab_docstrings'] = doc + # Substitute the docstrings into the proper file. Since the docs change + # every time the source is changed, we don't want to have to commit the + # change in the rst file as well as the source - too much code churn. So + # we use a template and a SubstFile directive. + c = tempenv.SubstFile('#doc/sphinx/matlab/code-docs/%s.rst' % page.name, + '#doc/sphinx/matlab/matlab-template.rst.in') + build(c) + localenv.Depends(sphinxdocs, c) + # Matlab examples: create individual documentation pages with the source # for each example for f in mglob(env, '#samples/matlab', 'm'): tmpenv = env.Clone() tmpenv['script_name'] = f.name tmpenv['script_path'] = '../../../../samples/matlab/%s' % f.name - b = tmpenv.SubstFile('#doc/sphinx/matlab/examples/%s.rst' % f.name[:-2], - '#doc/sphinx/matlab/example-script.rst.in') + if f.name.startswith('tut'): + b = tmpenv.SubstFile('#doc/sphinx/matlab/tutorials/%s.rst' % f.name[:-2], + '#doc/sphinx/matlab/example-script.rst.in') + else: + b = tmpenv.SubstFile('#doc/sphinx/matlab/examples/%s.rst' % f.name[:-2], + '#doc/sphinx/matlab/example-script.rst.in') build(b) localenv.Depends(sphinxdocs, b) diff --git a/doc/sphinx/compiling.rst b/doc/sphinx/compiling.rst index 876aac299..5ec0bddfc 100644 --- a/doc/sphinx/compiling.rst +++ b/doc/sphinx/compiling.rst @@ -522,6 +522,7 @@ Optional Programs * `Pygments `_ (install with ``easy_install -U pygments``) * `pyparsing `_ (install with ``easy_install -U pyparsing``) * `doxylink `_ (install with ``easy_install sphinxcontrib-doxylink``) + * `matlabdomain `_ (install with ``easy_install sphinxcontrib-matlabdomain``) * `Doxygen `_ diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index 308d5fbac..9c43fc27d 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -31,11 +31,19 @@ sys.path.append(os.path.abspath('./exts')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', + +# sphinxcontrib.matlab has been added to add the MATLAB domain for the +# documentation of the MATLAB functions. It is a new requirement to build the +# documentation in 2.2. It should be loaded before sphinx.ext.autodoc because +# loading it after gives errors when autodocumenting the Python interface. +extensions = [ + 'sphinxcontrib.matlab', + 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary', 'mathjax', - 'sphinxcontrib.doxylink'] + 'sphinxcontrib.doxylink', + ] # @todo: Sphinx version 1.1 adds support for MathJax, so we can remove the # custom extension for that once that version becomes more standard @@ -51,6 +59,10 @@ doxylink = { '../../doxygen/html/') } +# Ensure that the primary domain is the Python domain, since we've added the +# MATLAB domain with sphinxcontrib.matlab +primary_domain = 'py' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/sphinx/matlab/examples.rst b/doc/sphinx/matlab/examples.rst index 62d55ba1a..44a912c7f 100644 --- a/doc/sphinx/matlab/examples.rst +++ b/doc/sphinx/matlab/examples.rst @@ -1,6 +1,4 @@ -.. _sec-cython-examples: - -.. py:currentmodule:: cantera +.. _sec-matlab-examples: Index of Examples ================= @@ -12,7 +10,7 @@ Tutorials .. toctree:: :glob: - examples/tut* + tutorials/* Examples -------- @@ -20,4 +18,4 @@ Examples .. toctree:: :glob: - examples/[!tut]* + examples/* diff --git a/doc/sphinx/matlab/index.rst b/doc/sphinx/matlab/index.rst index dea27c75c..2052a2139 100644 --- a/doc/sphinx/matlab/index.rst +++ b/doc/sphinx/matlab/index.rst @@ -7,4 +7,13 @@ Matlab Interface User's Guide :maxdepth: 2 input-tutorial + code-docs/importing + code-docs/interface + code-docs/thermodynamics + code-docs/kinetics + code-docs/transport + code-docs/zero-dim + code-docs/one-dim + code-docs/data + code-docs/utilities examples diff --git a/doc/sphinx/matlab/matlab-template.rst.in b/doc/sphinx/matlab/matlab-template.rst.in new file mode 100644 index 000000000..0c837a738 --- /dev/null +++ b/doc/sphinx/matlab/matlab-template.rst.in @@ -0,0 +1,4 @@ + +@title@ + +@matlab_docstrings@