213 lines
6.5 KiB
Python
213 lines
6.5 KiB
Python
|
# -*- 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__}
|