blob: 6d896693907bba16ab0b37be24a68e57baf7ebe2 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version
# 2.0 (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
import os
import subprocess
from docutils import nodes
from os import path
from sphinx import addnodes
from sphinx.util import osutil
try:
from lxml import etree
except ImportError:
etree = None
# Run Doxygen on Read the Docs to generate XML files
if os.environ.get('READTHEDOCS'):
subprocess.call('doxygen')
if etree and path.isfile('xml/index.xml'):
# Doxygen files that have already been parsed
cache = {}
# Doxygen index
index = etree.parse('xml/index.xml')
def escape(name):
"""
Partial reimplementation in Python of Doxygen escapeCharsInString()
"""
return name.replace('_', '__').replace(':', '_1').replace('/', '_2').replace('<', '_3').replace('>', '_4').replace('*', '_5').replace('&', '_6').replace('|', '_7').replace('.', '_8').replace('!', '_9').replace(',', '_00').replace(' ', '_01').replace('{', '_02').replace('}', '_03').replace('?', '_04').replace('^', '_05').replace('%', '_06').replace('(', '_07').replace(')', '_08').replace('+', '_09').replace('=', '_0A').replace('$', '_0B').replace('\\', '_0C') # nopep8
class doctree_resolved:
"""
Add links from an API description to the source code for that object.
Doxygen knows where in the source code it's located.
Based on the sphinx.ext.viewcode and sphinx.ext.linkcode extensions.
"""
has_link = None
def __init__(self, app, doctree, docname):
self.app = app
self.docname = docname
self.traverse(doctree, None)
if self.has_link:
# Style the links
raw = nodes.raw(
'',
'<style> .rst-content dl dt .headerlink { display: inline-block } .rst-content dl dt .headerlink:after { visibility: hidden } .rst-content dl dt .viewcode-link { color: #2980b9; float: right; font-size: inherit; font-weight: normal } .rst-content dl dt:hover .headerlink:after { visibility: visible } </style>',
format='html')
doctree.insert(0, raw)
def traverse(self, node, owner):
"""
If an API description is nested in another description,
lookup the child in the context of the parent
"""
# nodes.Text iterates over characters, not children
for child in node.children:
if isinstance(child, addnodes.desc):
for desc_child in child.children:
if isinstance(desc_child, addnodes.desc_signature):
# Get the name of the object. An owner in the signature
# overrides an owner from a parent description.
signature_owner = None
for child in desc_child.children:
if isinstance(child, addnodes.desc_addname):
# An owner in the signature ends with ::
signature_owner = child.astext()[:-2]
elif isinstance(child, addnodes.desc_name):
name = child.astext()
break
# Lookup the object in the Doxygen index
try:
compound, = index.xpath(
'descendant::compound[(not($owner) or name[text() = $owner]) and descendant::name[text() = $name]][1]', owner=signature_owner or owner, name=name)
except ValueError:
continue
filename = compound.get('refid') + '.xml'
if filename not in cache:
cache[filename] = etree.parse('xml/' + filename)
# An enumvalue has no location
memberdef, = cache[filename].xpath(
'descendant::compounddef[compoundname[text() = $name]]', name=name) or cache[filename].xpath(
'descendant::memberdef[name[text() = $name] | enumvalue[name[text() = $name]]]', name=name)
# Append the link after the object's signature.
# Get the source file and line number from Doxygen and use
# them to construct the link.
location = memberdef.find('location')
filename = path.basename(location.get('file'))
# Declarations have no bodystart
line = location.get('bodystart') or location.get('line')
emphasis = nodes.emphasis('', ' ' + filename + ' line ' + line)
# Use a relative link if the output is HTML, otherwise fall
# back on an absolute link to Read the Docs. I haven't
# figured out how to get the page name for e.g. a struct
# from the XML files so ape Doxygen escapeCharsInString()
# instead.
refuri = 'api/' + escape(filename) + '_source.html#l' + line.rjust(5, '0')
if self.app.builder.name == 'html':
refuri = osutil.relative_uri(self.app.builder.get_target_uri(self.docname), refuri)
else:
refuri = 'http://docs.trafficserver.apache.org/en/latest/' + refuri
reference = nodes.reference('', '', emphasis, classes=[
'viewcode-link'], reftitle='Source code', refuri=refuri)
desc_child += reference
# Style the links
self.has_link = True
else:
self.traverse(desc_child, name)
else:
self.traverse(child, owner)
def setup(app):
if etree and path.isfile('xml/index.xml'):
# The doctree-read event hasn't got the docname argument
app.connect('doctree-resolved', doctree_resolved)
else:
if not etree:
app.warn('''Python lxml library not found
The library is used to add links from an API description to the source
code for that object.
Depending on your system, try installing the python-lxml package.''')
if not path.isfile('xml/index.xml'):
app.warn('''Doxygen files not found: xml/index.xml
The files are used to add links from an API description to the source
code for that object.
Run "$ make doxygen" to generate these XML files.''')