Files
cantera/doc/SConscript

298 lines
12 KiB
Python

from buildutils import *
import ast
Import('env', 'build', 'install')
localenv = env.Clone()
from collections import namedtuple
Page = namedtuple('Page', ['name', 'title', 'objects'])
def extract_python_docstring(pyfile, summary_only=True):
""" Returns the docstring from a Python script """
with open(pyfile) as f:
mod = ast.parse(f.read())
doc = ''
for node in mod.body:
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str):
doc = node.value.s
doc = doc.strip()
if summary_only:
doc = doc.split('\n\n')[0]
return doc
def extract_matlab_summary(mfile):
""" Returns a one-line summary comment from a .m file """
doc = ''
with open(mfile) as f:
for line in f:
line = line.strip()
if line.startswith('%'):
doc = line.strip('%').strip()
if doc:
break
name = os.path.basename(mfile)[:-2].replace('_', ' ')
if doc.lower().replace('_', ' ').startswith(name):
doc = doc[len(name):].strip()
return doc
# Set up functions to pseudo-autodoc the MATLAB toolbox
def extract_matlab_docstring(mfile, level):
"""
Return the docstring from mfile, assuming that it consists of the
first uninterrupted comment block.
:param mfile:
File name of the matlab file from which the documentation will be
read
:param level:
Level of documentation. Class = 0, Function = 1
"""
# Set the start of the docstring based on the level passed in. This is only
# necessary for the old-style MATLAB classes, where each method is its own
# file.
if level == 0:
docstring = ".. mat:class:: "
elif level == 1:
docstring = " .. mat:function:: "
else:
print "Unknown level for MATLAB documentation."
sys.exit(1)
# The leader is the number of spaces at the beginning of a regular line
# of documentation.
leader = ' '*4*(level + 1)
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 += leader + 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 += leader + 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']:
docs = build(localenv.Command('#build/docs/doxygen/html/index.html',
'doxygen/Doxyfile', 'doxygen $SOURCE'))
env.Depends(docs, env.Glob('#doc/doxygen/*') +
mglob(env, '#include/cantera', 'h') +
mglob(env, '#include/cantera/*', 'h') +
mglob(env, '#src/cantera/*', 'h', 'cpp'))
env.Alias('doxygen', docs)
install('$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
indexenv = env.Clone()
for subdir in subdirs(example_root):
summaries = []
for f in mglob(env, pjoin(example_root, subdir), 'py'):
docname = 'examples/{0}_{1}'.format(subdir, f.name[:-3])
summaries.append(':doc:`{0} <{1}>`:'.format(f.name, docname))
summaries.append(extract_python_docstring(f.abspath))
summaries.append('')
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/{0}.rst'.format(docname),
'#doc/sphinx/cython/example-script.rst.in')
build(b)
localenv.Depends(sphinxdocs, b)
indexenv['python_{0}_examples'.format(subdir)] = '\n'.join(summaries)
b = indexenv.SubstFile('#doc/sphinx/cython/examples.rst',
'#doc/sphinx/cython/examples.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',
'getDataDirectories.m', 'canteraVersion.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 = ['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)):
# Get a list of the functions in this class as long as its a file we care about
functions = [name for name in os.listdir(pjoin(base,obj)) if not any(x in name for x in nodoc_matlab_files)]
# Add the docstring for the class name at level 0
class_file = functions.pop(functions.index(obj.split('@')[1]+'.m'))
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,obj,class_file)), 0)
# Get the extra files from the extra dictionary and sort them with
# the regular functions.
extra_files = extra.get(obj,[])
all_files += sorted(functions + extra_files)
else:
all_files = extra.get(obj,[])
for file in all_files:
if file in functions:
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,obj,file)), 1)
else:
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,file)), 1)
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
examples = []
tutorials = []
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')
build(b)
localenv.Depends(sphinxdocs, b)
summary = [':doc:`{0} <examples/{1}>`:'.format(f.name, f.name[:-2]),
extract_matlab_summary(f.abspath),
'']
if f.name.startswith('tut'):
tutorials.extend(summary)
else:
examples.extend(summary)
localenv['matlab_tutorials'] = '\n'.join(tutorials)
localenv['matlab_examples'] = '\n'.join(examples)
b = localenv.SubstFile('#doc/sphinx/matlab/examples.rst',
'#doc/sphinx/matlab/examples.rst.in')
build(b)
localenv.Depends(sphinxdocs, b)
localenv.AlwaysBuild(sphinxdocs)