File: intersphinx_utils.py

package info (click to toggle)
beanbag-docutils 2.4-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 488 kB
  • sloc: python: 2,690; makefile: 202; sh: 8
file content (184 lines) | stat: -rw-r--r-- 6,270 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""Sphinx extension to enhance intersphinx support.

This fixes some reference issues with :rst:role:`option` (see
https://github.com/sphinx-doc/sphinx/pull/3769 for the equivalent upstream
fix).

It also introduces a ``.. default-intersphinx::`` directive that allows for
specifying one or more intersphinx set prefixes that should be tried if a
reference could not be found. For example::

    .. default-intersphinx:: myapp1.5 python

    :ref:`some-reference`

This does affect the process by which missing references are located. If an
unprefixed reference is used, it will only match if the prefix is in the list
above, which differs from the default behavior of looking through all
intersphinx mappings.


Setup
=====

This extension must be added to ``exetnsions`` in :file:`conf.py` after the
:py:mod:`sphinx.ext.intersphinx` extension is added. For example::

    extensions = [
        ...
        'sphinx.ext.intersphinx',
        'beanbag_docutils.sphinx.ext.intersphinx',
        ...
    ]
"""

import re

from docutils.parsers.rst import Directive
from sphinx.errors import ExtensionError
from sphinx.ext import intersphinx


class DefaultIntersphinx(Directive):
    """Specifies one or more default intersphinx sets to use."""

    required_arguments = 1
    optional_arguments = 100

    SPLIT_RE = re.compile(r',\s*')

    def run(self):
        """Run the directive.

        Returns:
            list:
            An empty list, always.
        """
        env = self.state.document.settings.env
        env.metadata[env.docname]['default-intersphinx-prefixes'] = \
            self.arguments

        return []


def _on_missing_reference(app, env, node, contnode):
    """Handler for missing references.

    This will attempt to fix references to options and then attempt to
    apply default intersphinx prefixes (if needed) before resolving a
    reference using intersphinx.

    Args:
        app (sphinx.application.Sphinx):
            The Sphinx application processing the document.

        env (sphinx.environment.BuildEnvironment):
            The environment for this doc build.

        node (sphinx.addnodes.pending_xref):
            The pending reference to resolve.

        contnode (docutils.nodes.literal):
            The context for the reference.

    Returns:
        list:
        The list of any reference nodes, as created by intersphinx.
    """
    orig_target = node['reftarget']
    target = orig_target
    domain = node.get('refdomain')

    # See if we're referencing a std:option. Sphinx (as of 1.6.1) does not
    # properly link these. A pull request has been opened to fix this
    # (https://github.com/sphinx-doc/sphinx/pull/3769). Until we can make
    # use of that, we're including the workaround here.
    if domain == 'std' and node['reftype'] == 'option':
        # Options are stored in the inventory as "program-name.--option".
        # In order to look up the option, the target will need to be
        # converted to this format.
        #
        # Ideally, we'd be able to utilize the same logic as found in
        # StandardDomain._resolve_option_xref, but we don't have any
        # information on the progoptions data stored there. Instead, we
        # try to determine if the target already has a program name or
        # an intersphinx doc set and normalize the contents to match the
        # option reference name format.
        i = target.rfind(' ')

        if i != -1:
            # The option target contains a program name and an option
            # name. We can easily normalize this to be in
            # <progname>.<option> format.
            target = '%s.%s' % (target[:i], target[i + 1:])
            target = target.replace(' ', '-')
        else:
            # Since a space was not found, and a program name is needed
            # to complete the reference, we'll see if a ".. program::"
            # has been set in this file. If so, we'll put that into the
            # target name (being careful to consider any intersphinx doc
            # set name that may be prefixed).
            progname = node.get('std:program')

            if progname:
                if ':' in target:
                    setname, newtarget = target.split(':', 1)
                    target = '%s:%s.%s' % (setname, progname, newtarget)
                else:
                    target = '%s.%s' % (progname, target)

    if ':' not in target:
        prefixes = \
            env.metadata[env.docname].get('default-intersphinx-prefixes')

        if prefixes:
            # Try all supported prefixes in order. These are the only allowed
            # to be inferred.
            for prefix in prefixes:
                old_content = contnode[0]
                node['reftarget'] = '%s:%s' % (prefix, target)
                result = intersphinx.missing_reference(app, env, node,
                                                       contnode)

                if result:
                    return result

                # Couldn't find it. Go back to the original target and try
                # again.
                node['reftarget'] = orig_target
                contnode[0] = old_content

            return None

    return intersphinx.missing_reference(app, env, node, contnode)


def setup(app):
    """Set up the Sphinx extension.

    This listens for the events needed to handle missing references, and
    registers directives.

    Args:
        app (sphinx.application.Sphinx):
            The Sphinx application building the docs.
    """
    app.add_directive('default-intersphinx', DefaultIntersphinx)

    # Disconnect the other intersphinx listener. We're going to override it.
    listeners = app.events.listeners.get('missing-reference', {})
    intersphinx_listener_id = None

    for listener in listeners:
        if listener.handler == intersphinx.missing_reference:
            intersphinx_listener_id = listener.id
            break

    if intersphinx_listener_id is not None:
        app.events.disconnect(intersphinx_listener_id)
    else:
        raise ExtensionError(
            'beanbag_docutils.sphinx.ext.intersphinx_utils '
            'must come after sphinx.ext.intersphinx')

    app.connect('missing-reference', _on_missing_reference)