from buildutils import * 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']) docs = build(localenv.Command('#build/docs/doxygen/html/index.html', 'doxygen/Doxyfile', 'doxygen $SOURCE')) env.Alias('doxygen', docs) install(pjoin('$inst_docdir', 'doxygen/html'), mglob(localenv, '#/build/docs/doxygen/html', 'html', 'svg', 'css', 'png')) if localenv['sphinx_docs']: localenv['SPHINXBUILD'] = Dir('#build/docs/sphinx') localenv['SPHINXSRC'] = Dir('sphinx') sphinxdocs = build(localenv.Command('${SPHINXBUILD}/html/index.html', 'sphinx/conf.py', '${sphinx_cmd} -b html -d ${SPHINXBUILD}/doctrees ${SPHINXSRC} ${SPHINXBUILD}/html')) env.Alias('sphinx', sphinxdocs) # Python examples: Create individual documentation pages with the source # for each example example_root = Dir('#interfaces/cython/cantera/examples').abspath for subdir in subdirs(example_root): for f in mglob(env, pjoin(example_root, subdir), 'py'): tmpenv = env.Clone() tmpenv['script_name'] = f.name tmpenv['script_path'] = '../../../../interfaces/cython/cantera/examples/%s/%s' % (subdir, f.name) b = tmpenv.SubstFile('#doc/sphinx/cython/examples/%s_%s.rst' % (subdir, f.name[:-3]), '#doc/sphinx/cython/example-script.rst.in') 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 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) localenv.AlwaysBuild(sphinxdocs)