Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 167be85

Browse files
committed
Improve path handling for modules
1 parent c6a2aba commit 167be85

File tree

1 file changed

+93
-55
lines changed

1 file changed

+93
-55
lines changed

doc/sphinxext/missing_references.py

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
from collections import defaultdict
1919
import json
2020
import logging
21-
import os.path
22-
import posixpath
21+
from pathlib import Path, PosixPath
2322

2423
from docutils.utils import get_source_line
2524
from docutils import nodes
2625
from sphinx.util import logging as sphinx_logging
2726

27+
import matplotlib
28+
2829
logger = sphinx_logging.getLogger(__name__)
2930

3031

@@ -42,11 +43,11 @@ def _record_reference(self, record):
4243
isinstance(getattr(record, 'location', None), nodes.Node)):
4344
return
4445

45-
if not hasattr(self.app.env, "missing_reference_warnings"):
46-
self.app.env.missing_reference_warnings = defaultdict(set)
46+
if not hasattr(self.app.env, "missing_references_warnings"):
47+
self.app.env.missing_references_warnings = defaultdict(set)
4748

4849
record_missing_reference(self.app,
49-
self.app.env.missing_reference_warnings,
50+
self.app.env.missing_references_warnings,
5051
record.location)
5152

5253
def filter(self, record):
@@ -60,9 +61,9 @@ def record_missing_reference(app, record, node):
6061
target = node["reftarget"]
6162
location = get_location(node, app)
6263

63-
dtype = "{}:{}".format(domain, typ)
64+
domain_type = "{}:{}".format(domain, typ)
6465

65-
record[(dtype, target)].add(location)
66+
record[(domain_type, target)].add(location)
6667

6768

6869
def record_missing_reference_handler(app, env, node, contnode):
@@ -75,10 +76,10 @@ def record_missing_reference_handler(app, env, node, contnode):
7576
# no-op when we are disabled.
7677
return
7778

78-
if not hasattr(env, "missing_reference_events"):
79-
env.missing_reference_events = defaultdict(set)
79+
if not hasattr(env, "missing_references_events"):
80+
env.missing_references_events = defaultdict(set)
8081

81-
record_missing_reference(app, env.missing_reference_events, node)
82+
record_missing_reference(app, env.missing_references_events, node)
8283

8384

8485
def get_location(node, app):
@@ -97,56 +98,92 @@ def get_location(node, app):
9798

9899
if path:
99100

100-
basepath = os.path.abspath(os.path.join(app.confdir, ".."))
101-
path = os.path.relpath(path, start=basepath)
101+
# We locate references relative to the parent of the doc
102+
# directory, which for matplotlib, will be the root of the
103+
# matplotlib repo. When matplotlib is not an editable install
104+
# wierd things will happen, but we can't totally recover from
105+
# that.
106+
basepath = Path(app.srcdir).parent.resolve()
107+
108+
fullpath = Path(path).resolve()
109+
110+
try:
111+
path = fullpath.relative_to(basepath)
112+
except ValueError:
113+
# Sometimes docs directly contain e.g. docstrings
114+
# from installed modules, and we record those as
115+
# <external> so as to be independent of where the
116+
# module was installed
117+
path = Path("<external>") / fullpath.name
102118

103-
if path.startswith(os.path.pardir):
104-
path = posixpath.join("<external>", os.path.basename(path))
119+
# Ensure that all reported paths are POSIX so that docs
120+
# on windows result in the same warnings in the JSON file.
121+
path = path.as_posix()
105122

106123
else:
107124
path = "<unknown>"
108125

109-
line = str(line) if line else ""
126+
if not line:
127+
line = ""
128+
110129
return f"{path}:{line}"
111130

112131

113-
def save_missing_references_handler(app, exc):
114-
"""
115-
At the end of the sphinx build, either save the missing references to a
116-
JSON file. Also ensure that all lines of the existing JSON file are still
117-
necessary.
118-
"""
119-
if not app.config.missing_references_enabled:
120-
# no-op when we are disabled.
132+
def _warn_unused_missing_references(app):
133+
if not app.config.missing_references_warn_unused_ignores:
121134
return
122135

123-
json_path = os.path.join(app.confdir,
124-
app.config.missing_references_filename)
136+
# We can only warn if we are building from a source install
137+
# otherwise, we just have to skip this step.
138+
basepath = Path(matplotlib.__file__).parent.parent.parent.resolve()
139+
srcpath = Path(app.srcdir).parent.resolve()
125140

126-
references_warnings = getattr(app.env, 'missing_reference_warnings', {})
141+
if basepath != srcpath:
142+
return
127143

128-
# This is a dictionary of {(dtype,target): locations}
144+
# This is a dictionary of {(domain_type, target): locations}
129145
references_ignored = getattr(app.env,
130146
'missing_references_ignored_references', {})
131-
references_events = getattr(app.env, 'missing_reference_events', {})
147+
references_events = getattr(app.env, 'missing_references_events', {})
132148

133149
# Warn about any reference which is no longer missing.
134-
for (dtype, target), locations in references_ignored.items():
150+
for (domain_type, target), locations in references_ignored.items():
135151
missing_reference_locations = references_events.get(
136-
(dtype, target), [])
152+
(domain_type, target), [])
137153

138-
# For each ignored reference location, ensure a missing reference was
139-
# observed. If it wasn't observed, issue a warning.
154+
# For each ignored reference location, ensure a missing reference
155+
# was observed. If it wasn't observed, issue a warning.
140156
for ignored_reference_location in locations:
141-
if ignored_reference_location not in missing_reference_locations:
142-
msg = (f"Reference {dtype} {target} for "
143-
f"{ignored_reference_location} can be removed"
144-
f" from {app.config.missing_references_filename}."
157+
if (ignored_reference_location not in
158+
missing_reference_locations):
159+
msg = (f"Reference {domain_type} {target} for "
160+
f"{ignored_reference_location} can be removed"
161+
f" from {app.config.missing_references_filename}."
145162
"It is no longer a missing reference in the docs.")
146163
logger.warning(msg,
147164
location=ignored_reference_location,
148165
type='ref',
149-
subtype=dtype)
166+
subtype=domain_type)
167+
168+
169+
def save_missing_references_handler(app, exc):
170+
"""
171+
At the end of the sphinx build, check that all lines of the existing JSON
172+
file are still necessary.
173+
174+
If the configuration value ``missing_references_write_json`` is set
175+
then write a new JSON file containing missing references.
176+
"""
177+
if not app.config.missing_references_enabled:
178+
# no-op when we are disabled.
179+
return
180+
181+
_warn_unused_missing_references(app)
182+
183+
json_path = (Path(app.confdir) /
184+
app.config.missing_references_filename)
185+
186+
references_warnings = getattr(app.env, 'missing_references_warnings', {})
150187

151188
if app.config.missing_references_write_json:
152189
_write_missing_references_json(references_warnings, json_path)
@@ -156,15 +193,15 @@ def _write_missing_references_json(records, json_path):
156193
"""
157194
Convert ignored references to a format which we can write as JSON
158195
159-
Convert from ``{(dtype, target): locaitons}`` to
160-
``{dtype: {target: locations}}`` since JSON can't serialize tuples.
196+
Convert from ``{(domain_type, target): locaitons}`` to
197+
``{domain_type: {target: locations}}`` since JSON can't serialize tuples.
161198
"""
162199
transformed_records = defaultdict(dict)
163200

164-
for (dtype, target), paths in records.items():
165-
transformed_records[dtype][target] = sorted(paths)
201+
for (domain_type, target), paths in records.items():
202+
transformed_records[domain_type][target] = sorted(paths)
166203

167-
with open(json_path, "w") as stream:
204+
with json_path.open("w") as stream:
168205
json.dump(transformed_records, stream, indent=2)
169206

170207

@@ -173,24 +210,24 @@ def _read_missing_references_json(json_path):
173210
Convert from the JSON file to the form used internally by this
174211
extension.
175212
176-
The JSON file is stored as ``{dtype: {target: [locations,]}}`` since JSON
177-
can't store dictionary keys which are tuples. We convert this back to
178-
``{(dtype,target):[locations]}`` for internal use.
213+
The JSON file is stored as ``{domain_type: {target: [locations,]}}``
214+
since JSON can't store dictionary keys which are tuples. We convert
215+
this back to ``{(domain_type, target):[locations]}`` for internal use.
179216
180217
"""
181-
with open(json_path, "r") as stream:
218+
with json_path.open("r") as stream:
182219
data = json.load(stream)
183220

184221
ignored_references = {}
185-
for dtype, targets in data.items():
222+
for domain_type, targets in data.items():
186223
for target, locations in targets.items():
187-
ignored_references[(dtype, target)] = locations
224+
ignored_references[(domain_type, target)] = locations
188225
return ignored_references
189226

190227

191228
def prepare_missing_references_handler(app):
192229
"""
193-
Handler called to initalize this extension once the configuration
230+
Handler called to initialize this extension once the configuration
194231
is ready.
195232
196233
Reads the missing references file and populates ``nitpick_ignore`` if
@@ -203,8 +240,8 @@ def prepare_missing_references_handler(app):
203240
sphinx_logger = logging.getLogger('sphinx')
204241
missing_reference_filter = MissingReferenceFilter(app)
205242
for handler in sphinx_logger.handlers[:]:
206-
if (isinstance(handler, sphinx_logging.WarningStreamHandler) and
207-
missing_reference_filter not in handler.filters):
243+
if (isinstance(handler, sphinx_logging.WarningStreamHandler)
244+
and missing_reference_filter not in handler.filters):
208245

209246
# This *must* be the first filter, because subsequent filters
210247
# throw away the node information and then we can't identify
@@ -213,16 +250,16 @@ def prepare_missing_references_handler(app):
213250

214251
app.env.missing_references_ignored_references = {}
215252

216-
json_path = os.path.join(app.confdir,
217-
app.config.missing_references_filename)
218-
if not os.path.exists(json_path):
253+
json_path = (Path(app.confdir) /
254+
app.config.missing_references_filename)
255+
if not json_path.exists():
219256
return
220257

221258
ignored_references = _read_missing_references_json(json_path)
222259

223260
app.env.missing_references_ignored_references = ignored_references
224261

225-
# If we are going to re-write the JSON file, then don't supress missing
262+
# If we are going to re-write the JSON file, then don't suppress missing
226263
# reference warnings. We want to record a full list of missing references
227264
# for use later. Otherwise, add all known missing references to
228265
# ``nitpick_ignore```
@@ -233,6 +270,7 @@ def prepare_missing_references_handler(app):
233270
def setup(app):
234271
app.add_config_value("missing_references_enabled", True, "env")
235272
app.add_config_value("missing_references_write_json", False, "env")
273+
app.add_config_value("missing_references_warn_unused_ignores", True, "env")
236274
app.add_config_value("missing_references_filename",
237275
"missing-references.json", "env")
238276

0 commit comments

Comments
 (0)