Add extension for source file cross referencing
parent
e50ea1e734
commit
56a05a6aa3
@ -0,0 +1,212 @@
|
||||
# -*- python -*-
|
||||
# This module provides the following project specific sphinx directives
|
||||
#
|
||||
# sourcefile
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
from sphinx import addnodes, roles
|
||||
from sphinx.util.nodes import make_refnode, set_source_info
|
||||
|
||||
_SOURCEFILES = 'cacert_sourcefiles'
|
||||
|
||||
__version__ = '0.1.0'
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class sourcefile_node(nodes.Structural, nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
def file_list(argument):
|
||||
if argument is None:
|
||||
return []
|
||||
else:
|
||||
file_names = [s.strip() for s in argument.splitlines()]
|
||||
return file_names
|
||||
|
||||
|
||||
class SourceFileRole(roles.XRefRole):
|
||||
def __init__(self, fix_parens=False, lowercase=False, nodeclass=None,
|
||||
warn_dangling=True):
|
||||
super().__init__(fix_parens, lowercase, nodeclass, nodes.literal,
|
||||
warn_dangling)
|
||||
|
||||
def process_link(self, env, refnode, has_explicit_title, title, target):
|
||||
return title, 'sourcefile-{}'.format(nodes.make_id(target))
|
||||
|
||||
def result_nodes(self, document, env, node, is_ref):
|
||||
try:
|
||||
indexnode = addnodes.index()
|
||||
targetid = 'index-%s' % env.new_serialno('index')
|
||||
targetnode = nodes.target('', '', ids=[targetid])
|
||||
doctitle = document.traverse(nodes.title)[0].astext()
|
||||
idxtext = "%s; %s" % (node.astext(), doctitle)
|
||||
idxtext2 = "%s; %s" % ('sourcefile', node.astext())
|
||||
indexnode['entries'] = [
|
||||
('single', idxtext, targetid, '', None),
|
||||
('single', idxtext2, targetid, '', None),
|
||||
]
|
||||
return [indexnode, targetnode, node], []
|
||||
except KeyError as e:
|
||||
return [node], [e.args[0]]
|
||||
|
||||
|
||||
def _source_file_info(env):
|
||||
if not hasattr(env, _SOURCEFILES):
|
||||
env.cacert_sourcefiles = {}
|
||||
return env.cacert_sourcefiles
|
||||
|
||||
|
||||
class SourceFile(Directive):
|
||||
"""
|
||||
A sourcefile entry in the form of an admonition.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
option_spec = {
|
||||
'uses': file_list,
|
||||
'links': file_list,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
|
||||
file_name = self.arguments[0]
|
||||
|
||||
target_id = 'sourcefile-{}'.format(nodes.make_id(file_name))
|
||||
section = nodes.section(ids=[target_id])
|
||||
|
||||
section += nodes.title(text=file_name)
|
||||
|
||||
par = nodes.paragraph()
|
||||
self.state.nested_parse(self.content, self.content_offset, par)
|
||||
|
||||
node = sourcefile_node()
|
||||
node.attributes['file_name'] = file_name
|
||||
node += section
|
||||
|
||||
_source_file_info(env)[file_name] = {
|
||||
'docname': env.docname,
|
||||
'lineno': self.lineno,
|
||||
'target_id': target_id,
|
||||
'uses': self.options.get('uses', []),
|
||||
'links': self.options.get('links', [])
|
||||
}
|
||||
|
||||
node += par
|
||||
set_source_info(self, node)
|
||||
|
||||
return [node]
|
||||
|
||||
|
||||
def _get_sourcefile_index_text(place_info):
|
||||
return "Source file; {}".format(place_info['filename'])
|
||||
|
||||
|
||||
def by_filename(item):
|
||||
return item[2].lower()
|
||||
|
||||
|
||||
def _add_reference_list(node, title, target_list, fromdocname, app):
|
||||
if target_list:
|
||||
para = nodes.paragraph()
|
||||
para += nodes.emphasis(text=title)
|
||||
items = nodes.bullet_list()
|
||||
para += items
|
||||
for item in sorted(target_list, key=by_filename):
|
||||
list_item = nodes.list_item()
|
||||
items += list_item
|
||||
refnode = nodes.reference('', '')
|
||||
innernode = nodes.literal(text=item[2])
|
||||
refnode['refdocname'] = item[0]
|
||||
refnode['refuri'] = "{}#{}".format(
|
||||
app.builder.get_relative_uri(fromdocname, item[0]),
|
||||
item[1])
|
||||
refnode += innernode
|
||||
refpara = nodes.paragraph()
|
||||
refpara += refnode
|
||||
list_item += refpara
|
||||
node.insert(-1, para)
|
||||
|
||||
|
||||
def process_sourcefiles(app, doctree):
|
||||
env = app.builder.env
|
||||
|
||||
source_file_info = _source_file_info(env)
|
||||
for node in doctree.traverse(sourcefile_node):
|
||||
file_name = node.attributes['file_name']
|
||||
info = source_file_info[file_name]
|
||||
outgoing_uses = [
|
||||
(item['docname'], item['target_id'], use)
|
||||
for item, use in [
|
||||
(source_file_info[use], use)
|
||||
for use in source_file_info[file_name]['uses']
|
||||
if use in source_file_info]]
|
||||
outgoing_links = [
|
||||
(item['docname'], item['target_id'], link)
|
||||
for item, link in [
|
||||
(source_file_info[link], link)
|
||||
for link in source_file_info[file_name]['links']
|
||||
if link in source_file_info]]
|
||||
incoming_uses = [
|
||||
(value['docname'], value['target_id'], key)
|
||||
for key, value in source_file_info.items()
|
||||
if file_name in value['uses']]
|
||||
incoming_links = [
|
||||
(value['docname'], value['target_id'], key)
|
||||
for key, value in source_file_info.items()
|
||||
if file_name in value['links']]
|
||||
_add_reference_list(
|
||||
node, 'Uses', outgoing_uses, env.docname, app)
|
||||
_add_reference_list(
|
||||
node, 'Links to', outgoing_links, env.docname, app)
|
||||
_add_reference_list(
|
||||
node, 'Used by', incoming_uses, env.docname, app)
|
||||
_add_reference_list(
|
||||
node, 'Linked from', incoming_links, env.docname, app)
|
||||
|
||||
|
||||
def resolve_missing_references(app, env, node, contnode):
|
||||
if node['reftype'] == 'sourcefile':
|
||||
target = [
|
||||
value for value in _source_file_info(env).values()
|
||||
if value['target_id'] == node['reftarget']]
|
||||
if len(target) == 1:
|
||||
return make_refnode(
|
||||
app.builder, node['refdoc'], target[0]['docname'],
|
||||
node['reftarget'], contnode)
|
||||
|
||||
|
||||
def purge_sourcefiles(app, env, docname):
|
||||
if not hasattr(env, 'cacert_sourcefiles'):
|
||||
return
|
||||
env.cacert_sourcefiles = dict([
|
||||
(key, value) for key, value in env.cacert_sourcefiles.items()
|
||||
if value['docname'] != docname])
|
||||
|
||||
|
||||
def visit_sourcefile_node(self, node):
|
||||
self.visit_admonition(node)
|
||||
|
||||
|
||||
def depart_sourcefile_node(self, node):
|
||||
self.depart_admonition(node)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_node(
|
||||
sourcefile_node,
|
||||
html=(visit_sourcefile_node, depart_sourcefile_node))
|
||||
|
||||
app.add_role('sourcefile', SourceFileRole())
|
||||
|
||||
app.add_directive('sourcefile', SourceFile)
|
||||
|
||||
app.connect('doctree-read', process_sourcefiles)
|
||||
app.connect('missing-reference', resolve_missing_references)
|
||||
app.connect('env-purge-doc', purge_sourcefiles)
|
||||
|
||||
return {'version': __version__}
|
Loading…
Reference in New Issue