From 0c904bebfae7c579a2af7cf8fd20e5e1062618fb Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Thu, 6 Sep 2012 19:58:13 +0000 Subject: [PATCH] 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. --- SConstruct | 4 + doc/SConscript | 2 +- doc/sphinx/conf.py | 12 +- doc/sphinx/cython/index.rst | 9 + doc/sphinx/cython/thermo.rst | 8 + doc/sphinx/exts/LICENSE.txt | 29 --- doc/sphinx/exts/doxylink.py | 446 ----------------------------------- doc/sphinx/exts/parsing.py | 153 ------------ doc/sphinx/index.rst | 1 + doc/sphinx/python/index.rst | 4 +- 10 files changed, 35 insertions(+), 633 deletions(-) create mode 100644 doc/sphinx/cython/index.rst create mode 100644 doc/sphinx/cython/thermo.rst delete mode 100644 doc/sphinx/exts/LICENSE.txt delete mode 100644 doc/sphinx/exts/doxylink.py delete mode 100644 doc/sphinx/exts/parsing.py diff --git a/SConstruct b/SConstruct index ee9d22089..18d397b8f 100644 --- a/SConstruct +++ b/SConstruct @@ -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 diff --git a/doc/SConscript b/doc/SConscript index 80533ba27..c11344322 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -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) diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index 7e0cc07a9..670b35ada 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -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 diff --git a/doc/sphinx/cython/index.rst b/doc/sphinx/cython/index.rst new file mode 100644 index 000000000..af47664a6 --- /dev/null +++ b/doc/sphinx/cython/index.rst @@ -0,0 +1,9 @@ +Python Module Documentation +=========================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + thermo diff --git a/doc/sphinx/cython/thermo.rst b/doc/sphinx/cython/thermo.rst new file mode 100644 index 000000000..77062e23a --- /dev/null +++ b/doc/sphinx/cython/thermo.rst @@ -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 diff --git a/doc/sphinx/exts/LICENSE.txt b/doc/sphinx/exts/LICENSE.txt deleted file mode 100644 index ef971ec98..000000000 --- a/doc/sphinx/exts/LICENSE.txt +++ /dev/null @@ -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. diff --git a/doc/sphinx/exts/doxylink.py b/doc/sphinx/exts/doxylink.py deleted file mode 100644 index 9b2195a5d..000000000 --- a/doc/sphinx/exts/doxylink.py +++ /dev/null @@ -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 element, use the parent compounds 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 then we assume it's a function. Empty 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 ` - 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) diff --git a/doc/sphinx/exts/parsing.py b/doc/sphinx/exts/parsing.py deleted file mode 100644 index 4e42f44b2..000000000 --- a/doc/sphinx/exts/parsing.py +++ /dev/null @@ -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 diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index b1dcf0ae9..5cd494835 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -27,6 +27,7 @@ Documentation language-interfaces cti/index python/index + cython/index matlab/index cxx-guide/index diff --git a/doc/sphinx/python/index.rst b/doc/sphinx/python/index.rst index 73e97e659..cc256e189 100644 --- a/doc/sphinx/python/index.rst +++ b/doc/sphinx/python/index.rst @@ -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: