Added infrastructure for using Sphinx to document the new Python module
This includes adding the sphinx_cmd option to SCons, which can be set to the Python 3 version of sphinx-build as necessary.
This commit is contained in:
parent
42e07afedc
commit
0c904bebfa
10 changed files with 35 additions and 633 deletions
|
|
@ -359,6 +359,10 @@ opts.AddVariables(
|
|||
'sphinx_docs',
|
||||
"""Build HTML documentation for the Python module using Sphinx""",
|
||||
False),
|
||||
PathVariable(
|
||||
'sphinx_cmd',
|
||||
"""Command to use for building the Sphinx documentation""",
|
||||
'sphinx-build', PathVariable.PathAccept),
|
||||
BoolVariable(
|
||||
'with_h298modify_capability',
|
||||
"""Enable changing the 298K heats of formation directly via
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ if localenv['sphinx_docs']:
|
|||
|
||||
sphinxdocs = build(localenv.Command('${SPHINXBUILD}/html/index.html',
|
||||
'sphinx/conf.py',
|
||||
'sphinx-build -b html -d ${SPHINXBUILD}/doctrees ${SPHINXSRC} ${SPHINXBUILD}/html'))
|
||||
'${sphinx_cmd} -b html -d ${SPHINXBUILD}/doctrees ${SPHINXSRC} ${SPHINXBUILD}/html'))
|
||||
|
||||
localenv.AlwaysBuild(sphinxdocs)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ import sys, os
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('../../interfaces/python'))
|
||||
if sys.version_info.major == 3:
|
||||
sys.path.insert(0, os.path.abspath('../../build/python3'))
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath('../../build/python2'))
|
||||
sys.path.insert(0, os.path.abspath('../../interfaces/python'))
|
||||
|
||||
sys.path.append(os.path.abspath('.'))
|
||||
sys.path.append(os.path.abspath('./exts'))
|
||||
|
||||
|
|
@ -31,7 +36,7 @@ extensions = ['sphinx.ext.autodoc',
|
|||
'sphinx.ext.todo',
|
||||
'sphinx.ext.autosummary',
|
||||
'mathjax',
|
||||
'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
|
||||
|
|
@ -85,6 +90,9 @@ release = '2.1a1'
|
|||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
if sys.version_info.major == 3:
|
||||
exclude_patterns.append('python/*')
|
||||
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
|
|
|||
9
doc/sphinx/cython/index.rst
Normal file
9
doc/sphinx/cython/index.rst
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Python Module Documentation
|
||||
===========================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
thermo
|
||||
8
doc/sphinx/cython/thermo.rst
Normal file
8
doc/sphinx/cython/thermo.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Thermodynamic Properties
|
||||
========================
|
||||
|
||||
These classes are used to describe the thermodynamic state of a system.
|
||||
|
||||
.. autoclass:: cantera.ThermoPhase
|
||||
.. autoclass:: cantera.InterfacePhase
|
||||
.. autoclass:: cantera.PureFluid
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
doxylink 1.2
|
||||
|
||||
Sphinx extension for linking to Doxygen documentation.
|
||||
|
||||
Copyright (c) 2011, Matt Williams.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -1,446 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
import urlparse
|
||||
import re
|
||||
import itertools
|
||||
|
||||
from docutils import nodes, utils
|
||||
from sphinx.util.nodes import split_explicit_title
|
||||
from sphinx.util.console import bold, standout
|
||||
|
||||
from parsing import normalise, ParseException
|
||||
|
||||
def find_url(doc, symbol):
|
||||
"""
|
||||
Return the URL for a given symbol.
|
||||
|
||||
This is where the magic happens.
|
||||
This function could be a lot more clever. At present it required the passed symbol to be almost exactly the same as the entries in the Doxygen tag file.
|
||||
|
||||
.. todo::
|
||||
|
||||
Maybe print a list of all possible matches as a warning (but still only return the first)
|
||||
|
||||
:Parameters:
|
||||
doc : xml.etree.ElementTree
|
||||
The XML DOM object
|
||||
symbol : string
|
||||
The symbol to lookup in the file. E.g. something like 'PolyVox::Array' or 'tidyUpMemory'
|
||||
|
||||
:return: String representing the filename part of the URL
|
||||
"""
|
||||
|
||||
#First check for an exact match with a top-level object (namespaces, objects etc.)
|
||||
|
||||
#env = inliner.document.settings.env
|
||||
|
||||
matches = []
|
||||
for compound in doc.findall('.//compound'):
|
||||
if compound.find('name').text == symbol:
|
||||
matches += [{'file':compound.find('filename').text, 'kind':compound.get('kind')}]
|
||||
|
||||
if len(matches) > 1:
|
||||
pass
|
||||
#env.warn(env.docname, 'There were multiple matches for `%s`: %s' % (symbol, matches))
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
|
||||
|
||||
#Strip off first namespace bit of the compound name so that 'ArraySizes' can match 'PolyVox::ArraySizes'
|
||||
for compound in doc.findall('.//compound'):
|
||||
symbol_list = compound.find('name').text.split('::', 1)
|
||||
if len(symbol_list) == 2:
|
||||
reducedsymbol = symbol_list[1]
|
||||
if reducedsymbol == symbol:
|
||||
return {'file':compound.find('filename').text, 'kind':compound.get('kind')}
|
||||
|
||||
#Now split the symbol by '::'. Find an exact match for the first part and then a member match for the second
|
||||
#So PolyVox::Array::operator[] becomes like {namespace: "PolyVox::Array", endsymbol: "operator[]"}
|
||||
symbol_list = symbol.rsplit('::', 1)
|
||||
if len(symbol_list) == 2:
|
||||
namespace = symbol_list[0]
|
||||
endsymbol = symbol_list[1]
|
||||
for compound in doc.findall('.//compound'):
|
||||
if compound.find('name').text == namespace:
|
||||
for member in compound.findall('member'):
|
||||
#If this compound object contains the matching member then return it
|
||||
if member.find('name').text == endsymbol:
|
||||
return {'file':(member.findtext('anchorfile') or compound.findtext('filename')) + '#' + member.find('anchor').text, 'kind':member.get('kind')}
|
||||
|
||||
#Then we'll look at unqualified members
|
||||
for member in doc.findall('.//member'):
|
||||
if member.find('name').text == symbol:
|
||||
return {'file':(member.findtext('anchorfile') or compound.findtext('filename')) + '#' + member.find('anchor').text, 'kind':member.get('kind')}
|
||||
|
||||
return None
|
||||
|
||||
def parse_tag_file(doc):
|
||||
"""
|
||||
Takes in an XML tree from a Doxygen tag file and returns a dictionary that looks something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'PolyVox': {'file': 'namespace_poly_vox.html',
|
||||
'kind': 'namespace'},
|
||||
'PolyVox::Array': {'file': 'class_poly_vox_1_1_array.html',
|
||||
'kind': 'class'},
|
||||
'PolyVox::Array1DDouble': {'file': 'namespace_poly_vox.html#a7a1f5fd5c4f7fbb4258a495d707b5c13',
|
||||
'kind': 'typedef'},
|
||||
'PolyVox::Array1DFloat': {'file': 'namespace_poly_vox.html#a879a120e49733eba1905c33f8a7f131b',
|
||||
'kind': 'typedef'},
|
||||
'PolyVox::Array1DInt16': {'file': 'namespace_poly_vox.html#aa1463ece448c6ebed55ab429d6ae3e43',
|
||||
'kind': 'typedef'},
|
||||
'QScriptContext::throwError': {'arglist': {'( Error error, const QString & text )': 'qscriptcontext.html#throwError',
|
||||
'( const QString & text )': 'qscriptcontext.html#throwError-2'},
|
||||
'kind': 'function'},
|
||||
'QScriptContext::toString': {'arglist': {'()': 'qscriptcontext.html#toString'},
|
||||
'kind': 'function'}}
|
||||
|
||||
Note the different form for functions. This is required to allow for 'overloading by argument type'.
|
||||
|
||||
To access a filename for a symbol you do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
symbol_mapping = mapping[symbol]
|
||||
if symbol_mapping['kind'] == 'function':
|
||||
url = symbol_mapping['arglist'][argument_string]
|
||||
else:
|
||||
url = symbol_mapping['file']
|
||||
|
||||
:Parameters:
|
||||
doc : xml.etree.ElementTree
|
||||
The XML DOM object
|
||||
|
||||
:return: a dictionary mapping fully qualified symbols to files
|
||||
"""
|
||||
|
||||
mapping = {}
|
||||
function_list = [] #This is a list of function to be parsed and inserted into mapping at the end of the function.
|
||||
for compound in doc.findall("./compound"):
|
||||
compound_kind = compound.get('kind')
|
||||
if compound_kind != 'namespace' and compound_kind != 'class':
|
||||
continue #Skip everything that isn't a namespace or class
|
||||
|
||||
compound_name = compound.findtext('name')
|
||||
compound_filename = compound.findtext('filename')
|
||||
|
||||
#If it's a compound we can simply add it
|
||||
mapping[compound_name] = {'kind' : compound_kind, 'file' : compound_filename}
|
||||
|
||||
for member in compound.findall('member'):
|
||||
|
||||
#If the member doesn't have an <anchorfile> element, use the parent compounds <filename> instead
|
||||
#This is the way it is in the qt.tag and is perhaps an artefact of old Doxygen
|
||||
anchorfile = member.findtext('anchorfile') or compound_filename
|
||||
member_symbol = join(compound_name, '::', member.findtext('name'))
|
||||
member_kind = member.get('kind')
|
||||
arglist_text = member.findtext('./arglist') #If it has an <arglist> then we assume it's a function. Empty <arglist> returns '', not None. Things like typedefs and enums can have empty arglists
|
||||
|
||||
if arglist_text and member_kind != 'variable' and member_kind != 'typedef' and member_kind != 'enumeration':
|
||||
function_list.append((member_symbol, arglist_text, member_kind, join(anchorfile,'#',member.findtext('anchor'))))
|
||||
else:
|
||||
mapping[member_symbol] = {'kind' : member.get('kind'), 'file' : join(anchorfile,'#',member.findtext('anchor'))}
|
||||
|
||||
for old_tuple, normalised_tuple in zip(function_list, itertools.imap(normalise, (member_tuple[1] for member_tuple in function_list))):
|
||||
member_symbol = old_tuple[0]
|
||||
original_arglist = old_tuple[1]
|
||||
kind = old_tuple[2]
|
||||
anchor_link = old_tuple[3]
|
||||
normalised_arglist = normalised_tuple[1]
|
||||
if normalised_tuple[1] is not None: #This is a 'flag' for a ParseException having happened
|
||||
if mapping.get(member_symbol):
|
||||
mapping[member_symbol]['arglist'][normalised_arglist] = anchor_link
|
||||
else:
|
||||
mapping[member_symbol] = {'kind' : kind, 'arglist' : {normalised_arglist : anchor_link}}
|
||||
else:
|
||||
print('Skipping %s %s%s. Error reported from parser was: %s' % (old_tuple[2], old_tuple[0], old_tuple[1], normalised_tuple[0]))
|
||||
|
||||
#from pprint import pprint; pprint(mapping)
|
||||
return mapping
|
||||
|
||||
def find_url2(mapping, symbol):
|
||||
"""
|
||||
Return the URL for a given symbol.
|
||||
|
||||
This is where the magic happens.
|
||||
|
||||
.. todo::
|
||||
|
||||
Maybe print a list of all possible matches as a warning (but still only return the first)
|
||||
|
||||
:Parameters:
|
||||
mapping : dictionary
|
||||
A dictionary of the form returned by :py:func:`parse_tag_file`
|
||||
symbol : string
|
||||
The symbol to lookup in the file. E.g. something like 'PolyVox::Array' or 'tidyUpMemory'
|
||||
|
||||
:return: String representing the filename part of the URL
|
||||
|
||||
:raises:
|
||||
LookupError
|
||||
Raised if the symbol could not be matched in the file
|
||||
"""
|
||||
#print "\n\nSearching for", symbol
|
||||
try:
|
||||
symbol, normalised_arglist = normalise(symbol)
|
||||
except ParseException as error:
|
||||
raise LookupError(error)
|
||||
#print symbol, normalised_arglist
|
||||
|
||||
#If we have an exact match then return it.
|
||||
if mapping.get(symbol):
|
||||
#print ('Exact match')
|
||||
return return_from_mapping(mapping[symbol], normalised_arglist)
|
||||
|
||||
#If the user didn't pass in any arguments, i.e. `arguments == ''` then they don't care which version of the overloaded funtion they get.
|
||||
|
||||
#First we check for any mapping entries which even slightly match the requested symbol
|
||||
#endswith_list = {}
|
||||
#for item, data in mapping.items():
|
||||
# if item.endswith(symbol):
|
||||
#print symbol + ' : ' + item
|
||||
# endswith_list[item] = data
|
||||
# mapping[item]['file']
|
||||
|
||||
#If we only find one then we return it.
|
||||
#if len(endswith_list) is 1:
|
||||
# return endswith_list.values()[0]['file']
|
||||
|
||||
#print("Still", len(endswith_list), 'possible matches')
|
||||
|
||||
piecewise_list = find_url_piecewise(mapping, symbol)
|
||||
|
||||
#If there is only one match, return it.
|
||||
if len(piecewise_list) is 1:
|
||||
return return_from_mapping(piecewise_list.values()[0], normalised_arglist)
|
||||
|
||||
#print("Still", len(piecewise_list), 'possible matches')
|
||||
|
||||
#If there is more than one item in piecewise_list then there is an ambiguity
|
||||
#Often this is due to the symbol matching the name of the constructor as well as the class name itself
|
||||
classes_list = find_url_classes(piecewise_list, symbol)
|
||||
|
||||
#If there is only one by here we return it.
|
||||
if len(classes_list) is 1:
|
||||
return classes_list.values()[0]
|
||||
|
||||
#print("Still", len(classes_list), 'possible matches')
|
||||
|
||||
#If we exhaused the list by requiring classes, use the list from before the filter.
|
||||
if len(classes_list) == 0:
|
||||
classes_list = piecewise_list
|
||||
|
||||
no_templates_list = find_url_remove_templates(classes_list, symbol)
|
||||
|
||||
if len(no_templates_list) is 1:
|
||||
return return_from_mapping(no_templates_list.values()[0], normalised_arglist)
|
||||
|
||||
#print("Still", len(no_templates_list), 'possible matches')
|
||||
|
||||
#If not found by now, just return the first one in the list
|
||||
if len(no_templates_list) != 0:
|
||||
#TODO return a warning here?
|
||||
return return_from_mapping(no_templates_list.values()[0], normalised_arglist)
|
||||
#Else return None if the list is empty
|
||||
else:
|
||||
LookupError('Could not find a match')
|
||||
|
||||
def return_from_mapping(mapping_entry, normalised_arglist=''):
|
||||
"""
|
||||
Return a mapping to a single URL in the form. This is needed since mapping entries for functions are more complicated due to function overriding.
|
||||
|
||||
If the mapping to be returned is not a function, this will simply return the mapping entry intact. If the entry is a function it will attempt to get the right version based on the function signature.
|
||||
|
||||
:Parameters:
|
||||
mapping_entry : dict
|
||||
should be a single entry from the large mapping file corresponding to a single symbol. If the symbol is a function, then ``mappingentry['arglist']`` will be a dictionary mapping normalised signatures to URLs
|
||||
normalised_arglist : string
|
||||
the normalised form of the arglist that the user has requested. This can be empty in which case the function will return just the first element of ``mappingentry['arglist']``. This parameter is ignored if ``mappingentry['kind'] != 'function'``
|
||||
|
||||
:return: dictionary something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'kind' : 'function', 'file' : 'something.html#foo'}
|
||||
|
||||
"""
|
||||
#If it's a function we need to grab the right signature from the arglist.
|
||||
if mapping_entry['kind'] == 'function':
|
||||
#If the user has requested a specific function through specifying an arglist then get the right anchor
|
||||
if normalised_arglist:
|
||||
filename = mapping_entry['arglist'].get(normalised_arglist)
|
||||
if not filename: #If we didn't get the filename because it's not in the mapping then we will just return a random one?
|
||||
#TODO return a warning here!
|
||||
filename = mapping_entry['arglist'].values()[0]
|
||||
else:
|
||||
#Otherwise just return the first entry (if they don't care they get whatever comes first)
|
||||
filename = mapping_entry['arglist'].values()[0]
|
||||
|
||||
return {'kind' : 'function', 'file' : filename}
|
||||
elif mapping_entry.get('arglist'):
|
||||
#This arglist should only be one entry long and that entry should have '' as its key
|
||||
return {'kind' : mapping_entry['kind'], 'file' : mapping_entry['arglist']['']}
|
||||
|
||||
#If it's not a function, then return it raw
|
||||
return mapping_entry
|
||||
|
||||
def find_url_piecewise(mapping, symbol):
|
||||
"""
|
||||
Match the requested symbol reverse piecewise (split on ``::``) against the tag names to ensure they match exactly (modulo ambiguity)
|
||||
So, if in the mapping there is ``PolyVox::Volume::FloatVolume`` and ``PolyVox::Volume`` they would be split into:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['PolyVox', 'Volume', 'FloatVolume'] and ['PolyVox', 'Volume']
|
||||
|
||||
and reversed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['FloatVolume', 'Volume', 'PolyVox'] and ['Volume', 'PolyVox']
|
||||
|
||||
and truncated to the shorter of the two:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['FloatVolume', 'Volume'] and ['Volume', 'PolyVox']
|
||||
|
||||
If we're searching for the ``PolyVox::Volume`` symbol we would compare:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['Volume', 'PolyVox'] to ['FloatVolume', 'Volume', 'PolyVox'].
|
||||
|
||||
That doesn't match so we look at the next in the mapping:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['Volume', 'PolyVox'] to ['Volume', 'PolyVox'].
|
||||
|
||||
Good, so we add it to the list
|
||||
|
||||
"""
|
||||
piecewise_list = {}
|
||||
for item, data in mapping.items():
|
||||
split_symbol = symbol.split('::')
|
||||
split_item = item.split('::')
|
||||
|
||||
split_symbol.reverse()
|
||||
split_item.reverse()
|
||||
|
||||
min_length = min(len(split_symbol), len(split_item))
|
||||
|
||||
split_symbol = split_symbol[:min_length]
|
||||
split_item = split_item[:min_length]
|
||||
|
||||
#print split_symbol, split_item
|
||||
|
||||
if split_symbol == split_item:
|
||||
#print symbol + ' : ' + item
|
||||
piecewise_list[item] = data
|
||||
|
||||
return piecewise_list
|
||||
|
||||
def find_url_classes(mapping, symbol):
|
||||
"""Prefer classes over names of constructors"""
|
||||
classes_list = {}
|
||||
for item, data in mapping.items():
|
||||
if data['kind'] == 'class':
|
||||
#print symbol + ' : ' + item
|
||||
classes_list[item] = data
|
||||
|
||||
return classes_list
|
||||
|
||||
def find_url_remove_templates(mapping, symbol):
|
||||
"""Now, to disambiguate between ``PolyVox::Array< 1, ElementType >::operator[]`` and ``PolyVox::Array::operator[]`` matching ``operator[]``, we will ignore templated (as in C++ templates) tag names by removing names containing ``<``"""
|
||||
no_templates_list = {}
|
||||
for item, data in mapping.items():
|
||||
if '<' not in item:
|
||||
#print symbol + ' : ' + item
|
||||
no_templates_list[item] = data
|
||||
|
||||
return no_templates_list
|
||||
|
||||
def join(*args):
|
||||
return ''.join(args)
|
||||
|
||||
def create_role(app, tag_filename, rootdir):
|
||||
#Tidy up the root directory path
|
||||
if not rootdir.endswith(('/', '\\')):
|
||||
rootdir = join(rootdir, os.sep)
|
||||
|
||||
try:
|
||||
tag_file = ET.parse(tag_filename)
|
||||
|
||||
cache_name = os.path.basename(tag_filename)
|
||||
|
||||
app.info(bold('Checking tag file cache for %s: ' % cache_name), nonl=True)
|
||||
if not hasattr(app.env, 'doxylink_cache'):
|
||||
# no cache present at all, initialise it
|
||||
app.info('No cache at all, rebuilding...')
|
||||
mapping = parse_tag_file(tag_file)
|
||||
app.env.doxylink_cache = { cache_name : {'mapping' : mapping, 'mtime' : os.path.getmtime(tag_filename)}}
|
||||
elif not app.env.doxylink_cache.get(cache_name):
|
||||
# Main cache is there but the specific sub-cache for this tag file is not
|
||||
app.info('Sub cache is missing, rebuilding...')
|
||||
mapping = parse_tag_file(tag_file)
|
||||
app.env.doxylink_cache[cache_name] = {'mapping' : mapping, 'mtime' : os.path.getmtime(tag_filename)}
|
||||
elif app.env.doxylink_cache[cache_name]['mtime'] < os.path.getmtime(tag_filename):
|
||||
# tag file has been modified since sub-cache creation
|
||||
app.info('Sub-cache is out of date, rebuilding...')
|
||||
mapping = parse_tag_file(tag_file)
|
||||
app.env.doxylink_cache[cache_name] = {'mapping' : mapping, 'mtime' : os.path.getmtime(tag_filename)}
|
||||
else:
|
||||
#The cache is up to date
|
||||
app.info('Sub-cache is up-to-date')
|
||||
except IOError:
|
||||
tag_file = None
|
||||
app.warn(standout('Could not open tag file %s. Make sure your `doxylink` config variable is set correctly.' % tag_filename))
|
||||
|
||||
def find_doxygen_link(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
text = utils.unescape(text)
|
||||
# from :name:`title <part>`
|
||||
has_explicit_title, title, part = split_explicit_title(text)
|
||||
warning_messages = []
|
||||
if tag_file:
|
||||
url = find_url(tag_file, part)
|
||||
try:
|
||||
url = find_url2(app.env.doxylink_cache[cache_name]['mapping'], part)
|
||||
except LookupError as error:
|
||||
warning_messages.append('Error while parsing `%s`. Is not a well-formed C++ function call or symbol. If this is not the case, it is a doxylink bug so please report it. Error reported was: %s' % (part, error))
|
||||
if url:
|
||||
|
||||
#If it's an absolute path then the link will work regardless of the document directory
|
||||
#Also check if it is a URL (i.e. it has a 'scheme' like 'http' or 'file')
|
||||
if os.path.isabs(rootdir) or urlparse.urlparse(rootdir).scheme:
|
||||
full_url = join(rootdir, url['file'])
|
||||
#But otherwise we need to add the relative path of the current document to the root source directory to the link
|
||||
else:
|
||||
relative_path_to_docsrc = os.path.relpath(app.env.srcdir, os.path.dirname(inliner.document.current_source))
|
||||
full_url = join(relative_path_to_docsrc, '/', rootdir, url['file']) #We always use the '/' here rather than os.sep since this is a web link avoids problems like documentation/.\../library/doc/ (mixed slashes)
|
||||
|
||||
if url['kind'] == 'function' and app.config.add_function_parentheses and not normalise(title)[1]:
|
||||
title = join(title, '()')
|
||||
|
||||
pnode = nodes.reference(title, title, internal=False, refuri=full_url)
|
||||
return [pnode], []
|
||||
#By here, no match was found
|
||||
warning_messages.append('Could not find match for `%s` in `%s` tag file' % (part, tag_filename))
|
||||
else:
|
||||
warning_messages.append('Could not find match for `%s` because tag file not found' % (part))
|
||||
|
||||
pnode = nodes.inline(rawsource=title, text=title)
|
||||
return [pnode], [inliner.reporter.warning(message, line=lineno) for message in warning_messages]
|
||||
|
||||
return find_doxygen_link
|
||||
|
||||
def setup_doxylink_roles(app):
|
||||
for name, [tag_filename, rootdir] in app.config.doxylink.iteritems():
|
||||
app.add_role(name, create_role(app, tag_filename, rootdir))
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('doxylink', {}, 'env')
|
||||
app.connect('builder-inited', setup_doxylink_roles)
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
#import multiprocessing
|
||||
import itertools
|
||||
|
||||
from pyparsing import Word, Literal, alphas, nums, alphanums, OneOrMore, Optional, SkipTo, ParseException, Group, ZeroOrMore, Suppress, Combine, delimitedList, quotedString, nestedExpr, ParseResults, oneOf
|
||||
|
||||
# define punctuation - reuse of expressions helps packratting work better
|
||||
LPAR,RPAR,LBRACK,RBRACK,COMMA,EQ = map(Literal,"()[],=")
|
||||
|
||||
#Qualifier to go in front of type in the argument list (unsigned const int foo)
|
||||
qualifier = OneOrMore(oneOf('const unsigned typename struct enum'))
|
||||
|
||||
def turn_parseresults_to_list(s, loc, toks):
|
||||
return ParseResults(normalise_templates(toks[0].asList()))
|
||||
|
||||
def normalise_templates(toks, isinstance=isinstance, basestring=basestring):
|
||||
s_list = ['<']
|
||||
s_list_append = s_list.append #lookup append func once, instead of many times
|
||||
for tok in toks:
|
||||
if isinstance(tok, basestring): #See if it's a string
|
||||
s_list_append(' ' + tok)
|
||||
else:
|
||||
#If it's not a string
|
||||
s_list_append(normalise_templates(tok))
|
||||
s_list_append(' >')
|
||||
return ''.join(s_list)
|
||||
|
||||
#Skip pairs of brackets.
|
||||
angle_bracket_pair = nestedExpr(opener='<',closer='>').setParseAction(turn_parseresults_to_list)
|
||||
#TODO Fix for nesting brackets
|
||||
parentheses_pair = LPAR + SkipTo(RPAR) + RPAR
|
||||
square_bracket_pair = LBRACK + SkipTo(RBRACK) + RBRACK
|
||||
|
||||
#The raw type of the input, i.e. 'int' in (unsigned const int * foo)
|
||||
#TODO I guess this should be a delimited list (by '::') of name and angle brackets
|
||||
input_type = Combine(Word(alphanums + ':_') + Optional(angle_bracket_pair + Optional(Word(alphanums + ':_'))))
|
||||
|
||||
#A number. e.g. -1, 3.6 or 5
|
||||
number = Word('-.' + nums)
|
||||
|
||||
#The name of the argument. We will ignore this but it must be matched anyway.
|
||||
input_name = OneOrMore(Word(alphanums + '_') | angle_bracket_pair | parentheses_pair | square_bracket_pair)
|
||||
|
||||
#Grab the '&', '*' or '**' type bit in (const QString & foo, int ** bar)
|
||||
pointer_or_reference = oneOf('* &')
|
||||
|
||||
#The '=QString()' or '=false' bit in (int foo = 4, bool bar = false)
|
||||
default_value = Literal('=') + OneOrMore(number | quotedString | input_type | parentheses_pair | angle_bracket_pair | square_bracket_pair | Word('|&^'))
|
||||
|
||||
#A combination building up the interesting bit -- the argument type, e.g. 'const QString &', 'int' or 'char*'
|
||||
argument_type = Optional(qualifier, default='')("qualifier") + \
|
||||
input_type("input_type") + \
|
||||
Optional(pointer_or_reference, default='')("pointer_or_reference1") + \
|
||||
Optional('const')('const_pointer_or_reference') + \
|
||||
Optional(pointer_or_reference, default='')("pointer_or_reference2")
|
||||
|
||||
#Argument + variable name + default
|
||||
argument = Group(argument_type('argument_type') + Optional(input_name) + Optional(default_value))
|
||||
|
||||
#List of arguments in parentheses with an optional 'const' on the end
|
||||
arglist = LPAR + delimitedList(argument)('arg_list') + Optional(COMMA + '...')('var_args') + RPAR
|
||||
|
||||
def normalise(symbol):
|
||||
"""
|
||||
Takes a c++ symbol or funtion and splits it into symbol and a normalised argument list.
|
||||
|
||||
:Parameters:
|
||||
symbol : string
|
||||
A C++ symbol or function definition like ``PolyVox::Volume``, ``Volume::printAll() const``
|
||||
|
||||
:return:
|
||||
a tuple consisting of two strings: ``(qualified function name or symbol, normalised argument list)``
|
||||
"""
|
||||
|
||||
try:
|
||||
bracket_location = symbol.index('(')
|
||||
#Split the input string into everything before the opening bracket and everything else
|
||||
function_name = symbol[:bracket_location]
|
||||
arglist_input_string = symbol[bracket_location:]
|
||||
except ValueError:
|
||||
#If there's no brackets, then there's no function signature. This means the passed in symbol is just a type name
|
||||
return symbol, ''
|
||||
|
||||
#This is a very common signature so we'll make a special case for it. It requires no parsing anyway
|
||||
if arglist_input_string.startswith('()'):
|
||||
if arglist_input_string in ('()', '()=0'):
|
||||
return function_name, arglist_input_string
|
||||
elif arglist_input_string in ('() const ', '() const', '() const =0'):
|
||||
return function_name, '() const'
|
||||
|
||||
#By now we're left with something like "(blah, blah)", "(blah, blah) const" or "(blah, blah) const =0"
|
||||
try:
|
||||
closing_bracket_location = arglist_input_string.rindex(')')
|
||||
arglist_suffix = arglist_input_string[closing_bracket_location+1:]
|
||||
arglist_input_string = arglist_input_string[:closing_bracket_location+1]
|
||||
except ValueError:
|
||||
#This shouldn't happen.
|
||||
print 'Could not find closing bracket in %s' % arglist_input_string
|
||||
raise
|
||||
|
||||
try:
|
||||
result = arglist.parseString(arglist_input_string)
|
||||
except ParseException as error:
|
||||
#print symbol
|
||||
#print pe
|
||||
return str(error), None
|
||||
else:
|
||||
#Will be a list or normalised string arguments
|
||||
#e.g. ['OBMol&', 'vector< int >&', 'OBBitVec&', 'OBBitVec&', 'int', 'int']
|
||||
normalised_arg_list = []
|
||||
|
||||
#Cycle through all the matched arguments
|
||||
for arg in result.arg_list:
|
||||
#Here is where we build up our normalised form of the argument
|
||||
argument_string_list = ['']
|
||||
if arg.qualifier:
|
||||
argument_string_list.append(''.join((arg.qualifier,' ')))
|
||||
argument_string_list.append(arg.input_type)
|
||||
|
||||
#Functions can have a funny combination of *, & and const between the type and the name so build up a list of theose here:
|
||||
const_pointer_ref_list = []
|
||||
const_pointer_ref_list.append(arg.pointer_or_reference1)
|
||||
if arg.const_pointer_or_reference:
|
||||
const_pointer_ref_list.append(''.join((' ', arg.const_pointer_or_reference, ' ')))
|
||||
# same here
|
||||
const_pointer_ref_list.append(arg.pointer_or_reference2)
|
||||
#And combine them into a single normalised string and add them to the argument list
|
||||
argument_string_list.extend(const_pointer_ref_list)
|
||||
|
||||
#Finally we join our argument string and add it to our list
|
||||
normalised_arg_list.append(''.join(argument_string_list))
|
||||
|
||||
#If the function contains a variable number of arguments (int foo, ...) then add them on.
|
||||
if result.var_args:
|
||||
normalised_arg_list.append('...')
|
||||
|
||||
#Combine all the arguments and put parentheses around it
|
||||
normalised_arg_list_string = ''.join(['(', ', '.join(normalised_arg_list), ')'])
|
||||
|
||||
#Add a const onto the end
|
||||
if 'const' in arglist_suffix:
|
||||
normalised_arg_list_string += ' const'
|
||||
|
||||
return function_name, normalised_arg_list_string
|
||||
|
||||
#TODO Maybe this should raise an exception?
|
||||
return None
|
||||
|
||||
def normalise_list(list_of_symbols):
|
||||
#normalise_pool = multiprocessing.Pool(multiprocessing.cpu_count() * 2)
|
||||
#results = normalise_pool.map(normalise, list_of_symbols)
|
||||
#normalise_pool.terminate()
|
||||
results = itertools.imap(normalise, list_of_symbols)
|
||||
return results
|
||||
|
|
@ -27,6 +27,7 @@ Documentation
|
|||
language-interfaces
|
||||
cti/index
|
||||
python/index
|
||||
cython/index
|
||||
matlab/index
|
||||
cxx-guide/index
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.. Cantera documentation master file, created by
|
||||
sphinx-quickstart on Mon Mar 12 11:43:09 2012.
|
||||
|
||||
Python Module Documentation
|
||||
===========================
|
||||
Python Module Documentation (Legacy)
|
||||
====================================
|
||||
|
||||
Contents:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue