diff --git a/source/conf.py b/source/conf.py index 5ebc900..4e0e14b 100644 --- a/source/conf.py +++ b/source/conf.py @@ -16,8 +16,8 @@ from datetime import datetime import os import certifi import requests -# import sys -# sys.path.insert(0, os.path.abspath('.')) +import sys +sys.path.insert(0, os.path.abspath('.')) from git import repo from docutils import nodes, utils @@ -65,6 +65,7 @@ extensions = [ 'sphinxcontrib.phpdomain', 'sphinxcontrib.blockdiag', 'sphinxcontrib.seqdiag', + 'sphinxext.cacert', ] # Add any paths that contain templates here, relative to this directory. diff --git a/source/sphinxext/__init__.py b/source/sphinxext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/sphinxext/cacert.py b/source/sphinxext/cacert.py new file mode 100644 index 0000000..30ac220 --- /dev/null +++ b/source/sphinxext/cacert.py @@ -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__}