Update doxylink to version 0.3
This commit is contained in:
parent
b127adfab8
commit
c82c0e666f
@ -7,6 +7,12 @@ Sphinx extension to link to external Doxygen API documentation.
|
|||||||
|
|
||||||
It works much like the extlinks extension but it does some more processing to link C++ symbols against their Doxygen HTML documentation.
|
It works much like the extlinks extension but it does some more processing to link C++ symbols against their Doxygen HTML documentation.
|
||||||
|
|
||||||
|
When generating your Doxygen documentation, you need to instruct it to create a 'tag' file. This is an XML file which contains the mapping between symbols and HTML files. To make Doxygen create this file ensure that you have a line like::
|
||||||
|
|
||||||
|
GENERATE_TAGFILE = PolyVox.tag
|
||||||
|
|
||||||
|
in your ``Doxyfile``.
|
||||||
|
|
||||||
.. confval:: doxylink
|
.. confval:: doxylink
|
||||||
|
|
||||||
The environment is set up with a dictionary mapping the interpereted text role
|
The environment is set up with a dictionary mapping the interpereted text role
|
||||||
@ -36,6 +42,8 @@ This allows one to do:
|
|||||||
Make the extension atuomatically re-run if the tag file is altered on disc
|
Make the extension atuomatically re-run if the tag file is altered on disc
|
||||||
|
|
||||||
Find a way to parse the tag file to a DOM tree *once* and then just reference it from then on.
|
Find a way to parse the tag file to a DOM tree *once* and then just reference it from then on.
|
||||||
|
|
||||||
|
Correct function overloading when arguments are given.
|
||||||
|
|
||||||
:copyright: Copyright 2010 by Matt Williams
|
:copyright: Copyright 2010 by Matt Williams
|
||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
@ -43,6 +51,8 @@ This allows one to do:
|
|||||||
|
|
||||||
from docutils import nodes, utils
|
from docutils import nodes, utils
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from sphinx.util.nodes import split_explicit_title
|
from sphinx.util.nodes import split_explicit_title
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@ -74,7 +84,7 @@ def find_url(doc, symbol):
|
|||||||
matches = []
|
matches = []
|
||||||
for compound in doc.findall('.//compound'):
|
for compound in doc.findall('.//compound'):
|
||||||
if compound.find('name').text == symbol:
|
if compound.find('name').text == symbol:
|
||||||
matches += [compound.find('filename').text]
|
matches += [{'file':compound.find('filename').text, 'kind':compound.get('kind')}]
|
||||||
|
|
||||||
if len(matches) > 1:
|
if len(matches) > 1:
|
||||||
pass
|
pass
|
||||||
@ -89,7 +99,7 @@ def find_url(doc, symbol):
|
|||||||
if len(symbol_list) == 2:
|
if len(symbol_list) == 2:
|
||||||
reducedsymbol = symbol_list[1]
|
reducedsymbol = symbol_list[1]
|
||||||
if reducedsymbol == symbol:
|
if reducedsymbol == symbol:
|
||||||
return compound.find('filename').text
|
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
|
#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[]"}
|
#So PolyVox::Array::operator[] becomes like {namespace: "PolyVox::Array", endsymbol: "operator[]"}
|
||||||
@ -102,39 +112,202 @@ def find_url(doc, symbol):
|
|||||||
for member in compound.findall('member'):
|
for member in compound.findall('member'):
|
||||||
# #If this compound object contains the matching member then return it
|
# #If this compound object contains the matching member then return it
|
||||||
if member.find('name').text == endsymbol:
|
if member.find('name').text == endsymbol:
|
||||||
return member.find('anchorfile').text + '#' + member.find('anchor').text
|
return {'file':member.find('anchorfile').text + '#' + member.find('anchor').text, 'kind':member.get('kind')}
|
||||||
|
|
||||||
#Then we'll look at unqualified members
|
#Then we'll look at unqualified members
|
||||||
for member in doc.findall('.//member'):
|
for member in doc.findall('.//member'):
|
||||||
if member.find('name').text == symbol:
|
if member.find('name').text == symbol:
|
||||||
return member.find('anchorfile').text + '#' + member.find('anchor').text
|
return {'file':member.find('anchorfile').text + '#' + member.find('anchor').text, 'kind':member.get('kind')}
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def parse_tag_file(doc):
|
||||||
|
"""
|
||||||
|
Takes in an XML free 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'}}
|
||||||
|
|
||||||
|
:Parameters:
|
||||||
|
doc : xml.etree.ElementTree
|
||||||
|
The XML DOM object
|
||||||
|
|
||||||
|
:return: a dictionary mapping fully qualified symbols to files
|
||||||
|
"""
|
||||||
|
mapping = {}
|
||||||
|
for compound in doc.findall(".//compound"):
|
||||||
|
if compound.get('kind') != 'namespace' and compound.get('kind') != 'class':
|
||||||
|
continue
|
||||||
|
|
||||||
|
mapping[compound.findtext('name')] = {'kind' : compound.get('kind'), 'file' : compound.findtext('filename')}
|
||||||
|
for member in compound.findall('member'):
|
||||||
|
mapping[join(compound.findtext('name'), '::', member.findtext('name'))] = {'kind' : member.get('kind'), 'file' : join(member.findtext('anchorfile'),'#',member.findtext('anchor')), 'arglist' : member.findtext('arglist')}
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
def find_url2(mapping, symbol):
|
||||||
|
print "\n\nSearching for", symbol
|
||||||
|
|
||||||
|
#If we have an exact match then return it.
|
||||||
|
if mapping.get(symbol):
|
||||||
|
return mapping[symbol]
|
||||||
|
|
||||||
|
#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 piecewise_list.values()[0]
|
||||||
|
|
||||||
|
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 no_templates_list.values()[0]
|
||||||
|
|
||||||
|
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:
|
||||||
|
return no_templates_list.values()[0]
|
||||||
|
#Else return None if the list is empty
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
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:
|
||||||
|
# ['PolyVox', 'Volume', 'FloatVolume'] and ['PolyVox', 'Volume']
|
||||||
|
#and reversed:
|
||||||
|
# ['FloatVolume', 'Volume', 'PolyVox'] and ['Volume', 'PolyVox']
|
||||||
|
#and truncated to the shorter of the two:
|
||||||
|
# ['FloatVolume', 'Volume'] and ['Volume', 'PolyVox']
|
||||||
|
#If we're searching for the "PolyVox::Volume" symbol we would compare:
|
||||||
|
# ['Volume', 'PolyVox'] to ['FloatVolume', 'Volume', 'PolyVox']. That doesn't match so we look at the next in the mapping:
|
||||||
|
# ['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):
|
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)
|
||||||
|
mapping = parse_tag_file(tag_file)
|
||||||
|
except (IOError):
|
||||||
|
tag_file = None
|
||||||
|
app.warn('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=[]):
|
def find_doxygen_link(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||||
text = utils.unescape(text)
|
text = utils.unescape(text)
|
||||||
# from :name:`title <part>`
|
# from :name:`title <part>`
|
||||||
has_explicit_title, title, part = split_explicit_title(text)
|
has_explicit_title, title, part = split_explicit_title(text)
|
||||||
|
|
||||||
try:
|
if tag_file:
|
||||||
tag_file = ET.parse(tag_filename)
|
|
||||||
except (IOError):
|
|
||||||
env = inliner.document.settings.env
|
|
||||||
env.warn(env.docname, 'Could not open %s' % tag_filename)
|
|
||||||
else:
|
|
||||||
url = find_url(tag_file, part)
|
url = find_url(tag_file, part)
|
||||||
if url:
|
if url:
|
||||||
full_url = rootdir + url
|
|
||||||
|
#If it's an absolute path then the link will work regardless of the document directory
|
||||||
|
if os.path.isabs(rootdir):
|
||||||
|
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, os.sep, rootdir, url['file'])
|
||||||
|
|
||||||
|
if url['kind'] == 'function' and app.config.add_function_parentheses:
|
||||||
|
title = join(title, '()')
|
||||||
|
|
||||||
pnode = nodes.reference(title, title, internal=False, refuri=full_url)
|
pnode = nodes.reference(title, title, internal=False, refuri=full_url)
|
||||||
return [pnode], []
|
return [pnode], []
|
||||||
#By here, no match was found
|
#By here, no match was found
|
||||||
env = app.env
|
env = app.env
|
||||||
env.warn(env.docname, 'Could not find match for `%s` in `%s` tag file' % (part, tag_filename), lineno)
|
env.warn(env.docname, 'Could not find match for `%s` in `%s` tag file' % (part, tag_filename), lineno)
|
||||||
|
else:
|
||||||
|
env = app.env
|
||||||
|
env.warn(env.docname, 'Could not find match for `%s` because tag file not found' % (part), lineno)
|
||||||
|
|
||||||
pnode = nodes.inline(rawsource=title, text=title)
|
pnode = nodes.inline(rawsource=title, text=title)
|
||||||
return [pnode], []
|
return [pnode], []
|
||||||
|
|
||||||
return find_doxygen_link
|
return find_doxygen_link
|
||||||
|
|
||||||
def setup_doxylink_roles(app):
|
def setup_doxylink_roles(app):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user