1"""Adapted from
2sphinx.transforms.post_transforms.ReferencesResolver.resolve_anyref
3
4If 'py' is one of the domains and `py:class` is defined,
5the Python domain will be processed before the 'std' domain.
6
7License for Sphinx
8==================
9
10Copyright (c) 2007-2019 by the Sphinx team (see AUTHORS file).
11All rights reserved.
12
13Redistribution and use in source and binary forms, with or without
14modification, are permitted provided that the following conditions are
15met:
16
17* Redistributions of source code must retain the above copyright
18  notice, this list of conditions and the following disclaimer.
19
20* Redistributions in binary form must reproduce the above copyright
21  notice, this list of conditions and the following disclaimer in the
22  documentation and/or other materials provided with the distribution.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35"""
36from contextlib import suppress
37
38from docutils import nodes
39from sphinx.transforms.post_transforms import ReferencesResolver
40
41
42class CustomReferencesResolver(ReferencesResolver):
43    def resolve_anyref(self, refdoc, node, contnode):
44        """Resolve reference generated by the "any" role."""
45        stddomain = self.env.get_domain("std")
46        target = node["reftarget"]
47
48        # process 'py' domain first for python classes
49        if "py:class" in node:
50            with suppress(KeyError):
51                py_domain = self.env.domains["py"]
52                py_ref = py_domain.resolve_any_xref(
53                    self.env, refdoc, self.app.builder, target, node, contnode
54                )
55                if py_ref:
56                    return self.create_node(py_ref[0])
57
58        # resolve :term:
59        term_ref = stddomain.resolve_xref(
60            self.env, refdoc, self.app.builder, "term", target, node, contnode
61        )
62        if term_ref:
63            # replace literal nodes with inline nodes
64            if not isinstance(term_ref[0], nodes.inline):
65                inline_node = nodes.inline(
66                    rawsource=term_ref[0].rawsource, classes=term_ref[0].get("classes")
67                )
68                if term_ref[0]:
69                    inline_node.append(term_ref[0][0])
70                term_ref[0] = inline_node
71            return self.create_node(("std:term", term_ref))
72
73        # next, do the standard domain
74        std_ref = stddomain.resolve_any_xref(
75            self.env, refdoc, self.app.builder, target, node, contnode
76        )
77        if std_ref:
78            return self.create_node(std_ref[0])
79
80        for domain in self.env.domains.values():
81            try:
82                ref = domain.resolve_any_xref(
83                    self.env, refdoc, self.app.builder, target, node, contnode
84                )
85                if ref:
86                    return self.create_node(ref[0])
87            except NotImplementedError:
88                # the domain doesn't yet support the new interface
89                # we have to manually collect possible references (SLOW)
90                for role in domain.roles:
91                    res = domain.resolve_xref(
92                        self.env, refdoc, self.app.builder, role, target, node, contnode
93                    )
94                    if res and isinstance(res[0], nodes.Element):
95                        result = ("%s:%s" % (domain.name, role), res)
96                        return self.create_node(result)
97
98        # no results considered to be <code>
99        contnode["classes"] = []
100        return contnode
101
102    def create_node(self, result):
103        res_role, newnode = result
104        # Override "any" class with the actual role type to get the styling
105        # approximately correct.
106        res_domain = res_role.split(":")[0]
107        if (
108            len(newnode) > 0
109            and isinstance(newnode[0], nodes.Element)
110            and newnode[0].get("classes")
111        ):
112            newnode[0]["classes"].append(res_domain)
113            newnode[0]["classes"].append(res_role.replace(":", "-"))
114        return newnode
115
116
117def setup(app):
118    if hasattr(app.registry, "get_post_transforms") and callable(
119        app.registry.get_post_transforms
120    ):
121        post_transforms = app.registry.get_post_transforms()
122    else:
123        # Support sphinx 1.6.*
124        post_transforms = app.post_transforms
125
126    for i, transform_class in enumerate(post_transforms):
127        if transform_class == ReferencesResolver:
128            post_transforms[i] = CustomReferencesResolver
129            break
130    else:
131        raise RuntimeError("ReferencesResolver not found")
132