forked from wandb/wandb
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcrossref.py
More file actions
108 lines (89 loc) · 3.38 KB
/
crossref.py
File metadata and controls
108 lines (89 loc) · 3.38 KB
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
# -*- coding: utf8 -*-
# This is an attempt to make crossref's work with :obj:`MyObject`, currently hardcoded locally
from nr.databind.core import Struct
from nr.interface import implements, override
from pydoc_markdown.interfaces import Processor, Resolver
from typing import Dict, List, Optional
import docspec
import logging
import re
logger = logging.getLogger(__name__)
@implements(Processor)
class CrossrefProcessor(Struct):
"""
Finds references to other objects in Markdown docstrings and produces links to other
pages. The links are provided by the current #Renderer via the #Resolver interface.
> __Note__: This processor is a work in progress, and most of the time it just converts
> references into inline-code.
The syntax for cross references is as follows:
```
This is a ref to another class: :obj:`PydocmdProcessor`
You can rename a ref like #this~PydocmdProcessor
And you can append to the ref name like this: #PydocmdProcessor#s
```
Renders as
> This is a ref to another class: :obj:`PydocmdProcessor`
> You can rename a ref like #this~PydocmdProcessor
> And you can append to the ref name like this: :obj:`PydocmdProcessor`s
Example configuration:
```yml
processors:
- type: crossref
```
"""
@override
def process(self, modules: List[docspec.Module], resolver: Optional[Resolver]):
unresolved = {}
if resolver:
reverse = docspec.ReverseMap(modules)
docspec.visit(
modules,
lambda x: self._preprocess_refs(x, resolver, reverse, unresolved),
)
if unresolved:
summary = []
for uid, refs in unresolved.items():
summary.append(" {}: {}".format(uid, ", ".join(refs)))
logger.warning(
"%s cross-reference(s) could not be resolved:\n%s",
sum(map(len, unresolved.values())),
"\n".join(summary),
)
def _preprocess_refs(
self,
node: docspec.ApiObject,
resolver: Resolver,
reverse: docspec.ReverseMap,
unresolved: Dict[str, List[str]],
) -> None:
if not node.docstring:
return
def handler(match):
ref = match.group("ref")
parens = match.group("parens") or ""
trailing = (match.group("trailing") or "").lstrip("#")
# Remove the dot from the ref if its trailing (it is probably just
# the end of the sentence).
has_trailing_dot = False
if trailing and trailing.endswith("."):
trailing = trailing[:-1]
has_trailing_dot = True
elif not parens and ref.endswith("."):
ref = ref[:-1]
has_trailing_dot = True
href = resolver.resolve_ref(node, ref)
if href:
result = "[`{}`]({})".format(ref + parens + trailing, href)
else:
uid = ".".join(x.name for x in reverse.path(node))
unresolved.setdefault(uid, []).append(ref)
result = "`{}`".format(ref + parens)
# Add back the dot.
if has_trailing_dot:
result += "."
return result
node.docstring = re.sub(
r"\B:obj:`(?P<ref>[\w\d\._]+)(?P<parens>\(\))?(?P<trailing>#[\w\d\._]+)?`",
handler,
node.docstring,
)