From bc87830fa2e3616bbd4f9e004aff2c327dbd2810 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 7 Oct 2023 00:09:23 +0300 Subject: [PATCH] extension: add support for source code links Sphinx has sphinx.ext.viewcode and sphinx.ext.linkcode extensions to fully incorporate source code into the documentation and to link to externally hosted source code, respectively. They are not trivial to integrate with Hawkmoth, however. Add a dedicated version of linkcode that can be configured with a simple template URI. --- doc/extension.rst | 21 ++++++++++++++++++ src/hawkmoth/__init__.py | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/doc/extension.rst b/doc/extension.rst index e6ffe9cd..9aca9b1c 100644 --- a/doc/extension.rst +++ b/doc/extension.rst @@ -85,6 +85,27 @@ See also additional configuration options in the :ref:`built-in extensions You can also pass in the compiler to use, for example ``get_include_args('gcc')``. +.. py:data:: hawkmoth_source_uri + :type: str + + A template URI to source code. If set, add links to externally hosted source + code for each documented symbol, similar to the :external+sphinx:doc:`Sphinx + linkcode extension `. Defaults to ``None``. + + The template URI will be formatted using + :external+python:py:meth:`str.format`, with the following replacement fields: + + ``{source}`` + Path to source file relative to :py:data:`hawkmoth_root`. + ``{line}`` + Line number in source file. + + Example: + + .. code-block:: python + + hawkmoth_source_uri = 'https://example.org/src/{source}#L{line}' + .. py:data:: cautodoc_root :type: str diff --git a/src/hawkmoth/__init__.py b/src/hawkmoth/__init__.py index 84892b19..bac4292c 100644 --- a/src/hawkmoth/__init__.py +++ b/src/hawkmoth/__init__.py @@ -13,6 +13,7 @@ from docutils import nodes from docutils.parsers.rst import directives from docutils.statemachine import ViewList +from sphinx import addnodes from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.docutils import switch_source_input, SphinxDirective from sphinx.util import logging @@ -303,6 +304,47 @@ class CppAutoClassDirective(_AutoCompoundDirective): _domain = 'cpp' _docstring_types = [docstring.ClassDocstring] +def _uri_format(env, signode): + """Generate a source URI for signode""" + uri_template = env.config.hawkmoth_source_uri + + if signode.source is None or signode.line is None: + return None + + source = os.path.relpath(signode.source, start=env.config.hawkmoth_root) + + # Note: magic +1 to take into account we've added signode ourselves and its + # not present in source + uri = uri_template.format(source=source, line=signode.line + 1) + + return uri + +def _doctree_read(app, doctree): + env = app.builder.env + + # Bail out early if not configured + uri_template = env.config.hawkmoth_source_uri + if uri_template is None: + return + + for objnode in list(doctree.findall(addnodes.desc)): + if objnode.get('domain') not in ['c', 'cpp']: + continue + + for signode in objnode: + if not isinstance(signode, addnodes.desc_signature): + continue + + uri = _uri_format(env, signode) + if not uri: + continue + + # Similar to sphinx.ext.linkcode + inline = nodes.inline('', '[source]', classes=['viewcode-link']) + onlynode = addnodes.only(expr='html') + onlynode += nodes.reference('', '', inline, internal=False, refuri=uri) + signode += onlynode + def _deprecate(conf, old, new, default=None): if conf[old]: logger = logging.getLogger(__name__) @@ -357,5 +399,9 @@ def setup(app): # Setup transformations for compatibility. app.setup_extension('hawkmoth.ext.transformations') + # Source code link + app.add_config_value('hawkmoth_source_uri', None, 'env', [str]) + app.connect('doctree-read', _doctree_read) + return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)