Sphinx

# Source code for sphinx.ext.linkcode

    """Add external links to module code in Python object descriptions."""

    from __future__ import annotations

    from types import FunctionType, NoneType
    from typing import TYPE_CHECKING

    from docutils import nodes

    import sphinx
    from sphinx import addnodes
    from sphinx.errors import SphinxError
    from sphinx.locale import _

    if TYPE_CHECKING:
        from docutils.nodes import Node

        from sphinx.application import Sphinx
        from sphinx.util.typing import ExtensionMetadata

    _DOMAIN_KEYS = {
        'py': ['module', 'fullname'],
        'c': ['names'],
        'cpp': ['names'],
        'js': ['object', 'fullname'],
    }

    def add_linkcode_domain(domain: str, keys: list[str], override: bool = False) -> None:
        """Register a new list of keys to use for a domain.

        .. versionadded:: 8.2
        """
        if override or domain not in _DOMAIN_KEYS:
            _DOMAIN_KEYS[domain] = list(keys)

    class LinkcodeError(SphinxError):
        category = 'linkcode error'

    def doctree_read(app: Sphinx, doctree: Node) -> None:
        env = app.env

        resolve_target = getattr(env.config, 'linkcode_resolve', None)
        if not callable(env.config.linkcode_resolve):
            msg = 'Function `linkcode_resolve` is not given in conf.py'
            raise LinkcodeError(msg)
        assert resolve_target is not None  # for mypy

        # By default, the linkcode extension will only inject references
        # for an ``html`` builder. If a builder wishes to support managing
        # references generated by linkcode as well, it can define the
        # ``supported_linkcode`` attribute.
        node_only_expr = getattr(app.builder, 'supported_linkcode', 'html')

        for objnode in list(doctree.findall(addnodes.desc)):
            domain = objnode.get('domain')
            uris: set[str] = set()
            for signode in objnode:
                if not isinstance(signode, addnodes.desc_signature):
                    continue

                # Convert signode to a specified format
                info = {}
                for key in _DOMAIN_KEYS.get(domain, ()):
                    value = signode.get(key)
                    if not value:
                        value = ''
                    info[key] = value
                if not info:
                    continue

                # Call user code to resolve the link
                uri = resolve_target(domain, info)
                if not uri:
                    # no source
                    continue

                if uri in uris or not uri:
                    # only one link per name, please
                    continue
                uris.add(uri)

                inline = nodes.inline('', _('[source]'), classes=['viewcode-link'])
                onlynode = addnodes.only(expr=node_only_expr)
                onlynode += nodes.reference('', '', inline, internal=False, refuri=uri)
                signode += onlynode

    def setup(app: Sphinx) -> ExtensionMetadata:
        app.connect('doctree-read', doctree_read)
        app.add_config_value(
            'linkcode_resolve', None, '', types=frozenset({FunctionType, NoneType})
        )
        return {
            'version': sphinx.__display_version__,
            'parallel_read_safe': True,
        }