1
1
import re
2
2
from sphinx import addnodes
3
3
4
+ # When sphinx (including the napoleon extension) parses the parameters
5
+ # section of a docstring, it converts the information into field lists.
6
+ # Some items in the list are for the parameter type. When the type fields
7
+ # are processed, the text is split and some tokens are turned into
8
+ # pending_xref nodes. These nodes are responsible for creating links.
9
+ #
10
+ # numpydoc does not create field lists, so the type information is
11
+ # not placed into fields that can be processed to make links. Instead,
12
+ # when parsing the type information we identify tokens that are link
13
+ # worthy and wrap them around a special role (xref_param_type_role).
14
+ # When the role is processed, we create pending_xref nodes which are
15
+ # later turned into links.
16
+
4
17
5
18
QUALIFIED_NAME_RE = re .compile (
6
- # e.g int, numpy.array, ~numpy.array
19
+ # e.g int, numpy.array, ~numpy.array, .class_in_current_module
7
20
r'^'
8
21
r'[~\.]?'
9
22
r'[a-zA-Z_]\w*'
10
23
r'(?:\.[a-zA-Z_]\w*)*'
11
24
r'$'
12
25
)
13
26
14
- CONTAINER_TYPE_RE = re .compile (
15
- # e.g.
16
- # - list[int]
17
- # - dict(str, int)
18
- # - dict[str, int]'
19
- # - tuple(float, float)
20
- # - dict[tuple(str, str), int]'
21
- r'^'
22
- r'(dict|list|tuple)'
23
- r'[\[\(]'
24
- r'(.+?(?:,\s*)?)+'
25
- r'[\]\)]'
26
- r'$'
27
- )
28
-
29
27
CONTAINER_SPLIT_RE = re .compile (
30
28
# splits dict(str, int) into
31
29
# ['dict', '[', 'str', ', ', 'int', ']', '']
32
- r'(\s*[\[\]\(\),]\s*)'
30
+ r'(\s*[\[\]\(\)\{\} ,]\s*)'
33
31
)
34
32
35
33
DOUBLE_QUOTE_SPLIT_RE = re .compile (
38
36
r'(``.+?``)'
39
37
)
40
38
41
- IGNORE = {'of' , ' of ' , 'either' , 'or' , 'with' , 'in' , 'default' }
39
+ ROLE_SPLIT_RE = re .compile (
40
+ # splits to preserve ReST roles
41
+ r'(:\w+:`.+?(?<!\\)`)'
42
+ )
43
+
44
+ TEXT_SPLIT_RE = re .compile (
45
+ # splits on ' or ', ' | ', ', ' and ' '
46
+ r'(\s+or\s+|\s+\|\s+|,\s+|\s+)'
47
+ )
48
+
49
+ IGNORE = {'of' , 'either' , 'or' , 'with' , 'in' , 'default' , 'optional' }
42
50
CONTAINER_CHARS = set ('[](){}' )
43
51
44
52
45
53
def make_xref_param_type (param_type , xref_aliases ):
46
54
"""
47
55
Enclose str in a role that creates a cross-reference
48
-
49
56
The role ``xref_param_type`` *may be* added to any token
50
57
that looks like type information and no other. The
51
58
function tries to be clever and catch type information
52
59
in different disguises.
53
-
54
60
Parameters
55
61
----------
56
62
param_type : str
57
63
text
58
64
xref_aliases : dict
59
65
Mapping used to resolve common abbreviations and aliases
60
66
to fully qualified names that can be cross-referenced.
61
-
62
67
Returns
63
68
-------
64
69
out : str
@@ -72,9 +77,6 @@ def make_xref_param_type(param_type, xref_aliases):
72
77
param_type not in IGNORE ):
73
78
return ':xref_param_type:`%s`' % param_type
74
79
75
- # Clever stuff below (except the last return)
76
- # can be removed without affecting the basic functionality.
77
-
78
80
def _split_and_apply_re (s , pattern ):
79
81
"""
80
82
Split string using the regex pattern,
@@ -94,16 +96,6 @@ def _split_and_apply_re(s, pattern):
94
96
return '' .join (results )
95
97
return s
96
98
97
- def _split_and_apply_str (s , on ):
98
- """
99
- Split string s, at the substring on,
100
- apply main function to the splits,
101
- combine the results
102
- """
103
- return on .join (
104
- make_xref_param_type (s , xref_aliases )
105
- for s in s .split (on ))
106
-
107
99
# The cases are dealt with in an order the prevents
108
100
# conflict.
109
101
# Then the strategy is:
@@ -112,37 +104,20 @@ def _split_and_apply_str(s, on):
112
104
# - re-apply the function to the other parts
113
105
# - join the results with the pattern
114
106
115
- # endswith ', optional'
116
- if param_type .endswith (', optional' ):
117
- return '%s, optional' % make_xref_param_type (
118
- param_type [:- 10 ],
119
- xref_aliases )
120
-
121
- # Any sort of bracket '[](){}'
122
- has_container = any (c in CONTAINER_CHARS for c in param_type )
123
- if has_container :
124
- # of the form 'dict[int, float]'
125
- if CONTAINER_TYPE_RE .match (param_type ):
126
- return _split_and_apply_re (param_type , CONTAINER_SPLIT_RE )
127
- else :
128
- # of the form '[int, float]'
129
- for start , end in ['[]' , '()' , '{}' ]:
130
- if param_type .startswith (start ) and param_type .endswith (end ):
131
- return '%s%s%s' % (
132
- start ,
133
- make_xref_param_type (param_type [1 :- 1 ], xref_aliases ),
134
- end )
135
-
136
- # May have an unsplittable literal
107
+ # Unsplittable literal
137
108
if '``' in param_type :
138
109
return _split_and_apply_re (param_type , DOUBLE_QUOTE_SPLIT_RE )
139
110
140
- # Is splittable
141
- for splitter in [' or ' , ', ' , ' ' ]:
142
- if splitter in param_type :
143
- return _split_and_apply_str (param_type , splitter )
111
+ # Any roles
112
+ if ':`' in param_type :
113
+ return _split_and_apply_re (param_type , ROLE_SPLIT_RE )
114
+
115
+ # Any sort of bracket '[](){}'
116
+ if any (c in CONTAINER_CHARS for c in param_type ):
117
+ return _split_and_apply_re (param_type , CONTAINER_SPLIT_RE )
144
118
145
- return param_type
119
+ # Common splitter tokens
120
+ return _split_and_apply_re (param_type , TEXT_SPLIT_RE )
146
121
147
122
148
123
def xref_param_type_role (role , rawtext , text , lineno , inliner ,
@@ -164,6 +139,6 @@ def xref_param_type_role(role, rawtext, text, lineno, inliner,
164
139
165
140
contnode = addnodes .literal_emphasis (text , text )
166
141
node = addnodes .pending_xref ('' , refdomain = 'py' , refexplicit = False ,
167
- reftype = 'obj ' , reftarget = target )
142
+ reftype = 'class ' , reftarget = target )
168
143
node += contnode
169
144
return [node ], []
0 commit comments