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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
from datetime import datetime
from urllib.parse import urljoin
import errno
import os
import urllib.request
from docutils import nodes
from lxml import html
import certifi
import jsonschema
__version__ = "1.2.0"
BASE_URL = "https://json-schema.org/draft-07/"
VALIDATION_SPEC = urljoin(BASE_URL, "json-schema-validation.html")
REF_URL = urljoin(BASE_URL, "json-schema-core.html#rfc.section.8.3")
SCHEMA_URL = urljoin(BASE_URL, "json-schema-core.html#rfc.section.7")
def setup(app):
"""
Install the plugin.
Arguments:
app (sphinx.application.Sphinx):
the Sphinx application context
"""
app.add_config_value("cache_path", "_cache", "")
try:
os.makedirs(app.config.cache_path)
except OSError as error:
if error.errno != errno.EEXIST:
raise
path = os.path.join(app.config.cache_path, "spec.html")
spec = fetch_or_load(path)
app.add_role("validator", docutils_does_not_allow_using_classes(spec))
return dict(version=__version__, parallel_read_safe=True)
def fetch_or_load(spec_path):
"""
Fetch a new specification or use the cache if it's current.
Arguments:
cache_path:
the path to a cached specification
"""
headers = {
"User-Agent": "python-jsonschema v{} - documentation build v{}".format(
jsonschema.__version__, __version__,
),
}
try:
modified = datetime.utcfromtimestamp(os.path.getmtime(spec_path))
date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC")
headers["If-Modified-Since"] = date
except OSError as error:
if error.errno != errno.ENOENT:
raise
request = urllib.request.Request(VALIDATION_SPEC, headers=headers)
response = urllib.request.urlopen(request, cafile=certifi.where())
if response.code == 200:
with open(spec_path, "w+b") as spec:
spec.writelines(response)
spec.seek(0)
return html.parse(spec)
with open(spec_path) as spec:
return html.parse(spec)
def docutils_does_not_allow_using_classes(spec):
"""
Yeah.
It doesn't allow using a class because it does annoying stuff like
try to set attributes on the callable object rather than just
keeping a dict.
"""
def validator(name, raw_text, text, lineno, inliner):
"""
Link to the JSON Schema documentation for a validator.
Arguments:
name (str):
the name of the role in the document
raw_source (str):
the raw text (role with argument)
text (str):
the argument given to the role
lineno (int):
the line number
inliner (docutils.parsers.rst.states.Inliner):
the inliner
Returns:
tuple:
a 2-tuple of nodes to insert into the document and an
iterable of system messages, both possibly empty
"""
if text == "$ref":
return [nodes.reference(raw_text, text, refuri=REF_URL)], []
elif text == "$schema":
return [nodes.reference(raw_text, text, refuri=SCHEMA_URL)], []
# find the header in the validation spec containing matching text
header = spec.xpath("//h1[contains(text(), '{0}')]".format(text))
if len(header) == 0:
inliner.reporter.warning(
"Didn't find a target for {0}".format(text),
)
uri = VALIDATION_SPEC
else:
if len(header) > 1:
inliner.reporter.info(
"Found multiple targets for {0}".format(text),
)
# get the href from link in the header
uri = urljoin(VALIDATION_SPEC, header[0].find("a").attrib["href"])
reference = nodes.reference(raw_text, text, refuri=uri)
return [reference], []
return validator
|