17
17
18
18
from collections import defaultdict
19
19
import json
20
- import logging
21
20
from pathlib import Path
22
21
23
22
from docutils .utils import get_source_line
24
- from docutils import nodes
25
23
from sphinx .util import logging as sphinx_logging
26
24
27
25
import matplotlib
28
26
29
27
logger = sphinx_logging .getLogger (__name__ )
30
28
31
29
32
- class MissingReferenceFilter (logging .Filter ):
33
- """
34
- A logging filter designed to record missing reference warning messages
35
- for use by this extension
36
- """
37
- def __init__ (self , app ):
38
- self .app = app
39
- super ().__init__ ()
40
-
41
- def _record_reference (self , record ):
42
- if not (getattr (record , 'type' , '' ) == 'ref' and
43
- isinstance (getattr (record , 'location' , None ), nodes .Node )):
44
- return
45
-
46
- if not hasattr (self .app .env , "missing_references_warnings" ):
47
- self .app .env .missing_references_warnings = defaultdict (set )
48
<
10000
/td>-
49
- record_missing_reference (self .app ,
50
- self .app .env .missing_references_warnings ,
51
- record .location )
52
-
53
- def filter (self , record ):
54
- self ._record_reference (record )
55
- return True
56
-
57
-
58
- def record_missing_reference (app , record , node ):
59
- domain = node ["refdomain" ]
60
- typ = node ["reftype" ]
61
- target = node ["reftarget" ]
62
- location = get_location (node , app )
63
-
64
- domain_type = f"{ domain } :{ typ } "
65
-
66
- record [(domain_type , target )].add (location )
67
-
68
-
69
- def record_missing_reference_handler (app , env , node , contnode ):
70
- """
71
- When the sphinx app notices a missing reference, it emits an
72
- event which calls this function. This function records the missing
73
- references for analysis at the end of the sphinx build.
74
- """
75
- if not app .config .missing_references_enabled :
76
- # no-op when we are disabled.
77
- return
78
-
79
- if not hasattr (env , "missing_references_events" ):
80
- env .missing_references_events = defaultdict (set )
81
-
82
- record_missing_reference (app , env .missing_references_events , node )
83
-
84
-
85
30
def get_location (node , app ):
86
31
"""
87
32
Given a docutils node and a sphinx application, return a string
@@ -146,10 +91,34 @@ def _truncate_location(location):
146
91
return location .split (":" , 1 )[0 ]
147
92
148
93
149
- def _warn_unused_missing_references (app ):
150
- if not app .config .missing_references_warn_unused_ignores :
151
- return
94
+ def handle_missing_reference (app , domain , node ):
95
+ """
96
+ Handle the warn-missing-reference Sphinx event.
97
+
98
+ This function will:
152
99
100
+ #. record missing references for saving/comparing with ignored list.
101
+ #. prevent Sphinx from raising a warning on ignored references.
102
+ """
103
+ typ = node ["reftype" ]
104
+ target = node ["reftarget" ]
105
+ location = get_location (node , app )
106
+ domain_type = f"{ domain .name } :{ typ } "
107
+
108
+ app .env .missing_references_events [(domain_type , target )].add (location )
109
+
110
+ # If we're ignoring this event, return True so that Sphinx thinks we handled it,
111
+ # even though we didn't print or warn. If we aren't ignoring it, Sphinx will print a
112
+ # warning about the missing reference.
113
+ if location in app .env .missing_references_ignored_references .get (
114
+ (domain_type , target ), []):
115
+ return True
116
+
117
+
118
+ def warn_unused_missing_references (app , exc ):
119
+ """
120
+ Check that all lines of the existing JSON file are still necessary.
121
+ """
153
122
# We can only warn if we are building from a source install
154
123
# otherwise, we just have to skip this step.
155
124
basepath = Path (matplotlib .__file__ ).parent .parent .parent .resolve ()
@@ -159,9 +128,8 @@ def _warn_unused_missing_references(app):
159
128
return
160
129
161
130
# This is a dictionary of {(domain_type, target): locations}
162
- references_ignored = getattr (
163
- app .env , 'missing_references_ignored_references' , {})
164
- references_events = getattr (app .env , 'missing_references_events' , {})
131
+ references_ignored = app .env .missing_references_ignored_references
132
+ references_events = app .env .missing_references_ev
179B
ents
165
133
166
134
# Warn about any reference which is no longer missing.
167
135
for (domain_type , target ), locations in references_ignored .items ():
@@ -184,26 +152,13 @@ def _warn_unused_missing_references(app):
184
152
subtype = domain_type )
185
153
186
154
187
- def save_missing_references_handler (app , exc ):
155
+ def save_missing_references (app , exc ):
188
156
"""
189
- At the end of the sphinx build, check that all lines of the existing JSON
190
- file are still necessary.
191
-
192
- If the configuration value ``missing_references_write_json`` is set
193
- then write a new JSON file containing missing references.
157
+ Write a new JSON file containing missing references.
194
158
"""
195
- if not app .config .missing_references_enabled :
196
- # no-op when we are disabled.
197
- return
198
-
199
- _warn_unused_missing_references (app )
200
-
201
159
json_path = Path (app .confdir ) / app .config .missing_references_filename
202
-
203
- references_warnings = getattr (app .env , 'missing_references_warnings' , {})
204
-
205
- if app .config .missing_references_write_json :
206
- _write_missing_references_json (references_warnings , json_path )
160
+ references_warnings = app .env .missing_references_events
161
+ _write_missing_references_json (references_warnings , json_path )
207
162
208
163
209
164
def _write_missing_references_json (records , json_path ):
@@ -220,6 +175,7 @@ def _write_missing_references_json(records, json_path):
220
175
transformed_records [domain_type ][target ] = sorted (paths )
221
176
with json_path .open ("w" ) as stream :
222
177
json .dump (transformed_records , stream , sort_keys = True , indent = 2 )
178
+ stream .write ("\n " ) # Silence pre-commit no-newline-at-end-of-file warning.
223
179
224
180
225
181
def _read_missing_references_json (json_path ):
@@ -242,49 +198,25 @@ def _read_missing_references_json(json_path):
242
198
return ignored_references
243
199
244
200
245
- def prepare_missing_references_handler (app ):
201
+ def prepare_missing_references_setup (app ):
246
202
"""
247
- Handler called to initialize this extension once the configuration
248
- is ready.
249
-
250
- Reads the missing references file and populates ``nitpick_ignore`` if
251
- appropriate.
203
+ Initialize this extension once the configuration is ready.
252
204
"""
253
205
if not app .config .missing_references_enabled :
254
206
# no-op when we are disabled.
255
207
return
256
208
257
- sphinx_logger = logging .getLogger ('sphinx' )
258
- missing_reference_filter = MissingReferenceFilter (app )
259
- for handler in sphinx_logger .handlers :
260
- if (isinstance (handler , sphinx_logging .WarningStreamHandler )
261
- and missing_reference_filter not in handler .filters ):
262
-
263
- # This *must* be the first filter, because subsequent filters
264
- # throw away the node information and then we can't identify
265
- # the reference uniquely.
266
- handler .filters .insert (0 , missing_reference_filter )
267
-
268
- app .env .missing_references_ignored_references = {}
209
+ app .connect ("warn-missing-reference" , handle_missing_reference )
210
+ if app .config .missing_references_warn_unused_ignores :
211
+ app .connect ("build-finished" , warn_unused_missing_references )
212
+ if app .config .missing_references_write_json :
213
+ app .connect ("build-finished" , save_missing_references )
269
214
270
215
json_path = Path (app .confdir ) / app .config .missing_references_filename
271
- if not json_path .exists ():
272
- return
273
-
274
- ignored_references = _read_missing_references_json (json_path )
275
-
276
- app .env .missing_references_ignored_references = ignored_references
277
-
278
- # If we are going to re-write the JSON file, then don't suppress missing
279
- # reference warnings. We want to record a full list of missing references
280
- # for use later. Otherwise, add all known missing references to
281
- # ``nitpick_ignore```
282
- if not app .config .missing_references_write_json :
283
- # Since Sphinx v6.2, nitpick_ignore may be a list, set or tuple, and
284
- # defaults to set. Previously it was always a list. Cast to list for
285
- # consistency across versions.
286
- app .config .nitpick_ignore = list (app .config .nitpick_ignore )
287
- app .config .nitpick_ignore .extend (ignored_references .keys ())
216
+ app .env .missing_references_ignored_references = (
217
+ _read_missing_references_json (json_path ) if json_path .exists () else {}
218
+ )
219
+ app .env .missing_references_events = defaultdict (set )
288
220
289
221
290
222
def setup (app ):
@@ -294,8 +226,6 @@ def setup(app):
294
226
app .add_config_value ("missing_references_filename" ,
295
227
"missing-references.json" , "env" )
296
228
297
- app .connect ("builder-inited" , prepare_missing_references_handler )
298
- app .connect ("missing-reference" , record_missing_reference_handler )
299
- app .connect ("build-finished" , save_missing_references_handler )
229
+ app .connect ("builder-inited" , prepare_missing_references_setup )
300
230
301
231
return {'parallel_read_safe' : True }
0 commit comments