Skip to content

Commit 22fd791

Browse files
pawlJulian
authored andcommitted
fix validation spec parsing for docs
1 parent bc958e9 commit 22fd791

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

sphinx_json_schema_spec/__init__.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from datetime import datetime
2+
from docutils import nodes
3+
import errno
4+
import os
5+
6+
try:
7+
import urllib2 as urllib
8+
except ImportError:
9+
import urllib.request as urllib
10+
11+
from lxml import html
12+
13+
14+
VALIDATION_SPEC = "http://json-schema.org/latest/json-schema-validation.html"
15+
16+
17+
def setup(app):
18+
"""
19+
Install the plugin.
20+
21+
:argument sphinx.application.Sphinx app: the Sphinx application context
22+
23+
"""
24+
25+
app.add_config_value("cache_path", "_cache", "")
26+
27+
try:
28+
os.makedirs(app.config.cache_path)
29+
except OSError as error:
30+
if error.errno != errno.EEXIST:
31+
raise
32+
33+
path = os.path.join(app.config.cache_path, "spec.html")
34+
spec = fetch_or_load(path)
35+
app.add_role("validator", docutils_sucks(spec))
36+
37+
38+
def fetch_or_load(spec_path):
39+
"""
40+
Fetch a new specification or use the cache if it's current.
41+
42+
:argument cache_path: the path to a cached specification
43+
44+
"""
45+
46+
headers = {}
47+
48+
try:
49+
modified = datetime.utcfromtimestamp(os.path.getmtime(spec_path))
50+
date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC")
51+
headers["If-Modified-Since"] = date
52+
except OSError as error:
53+
if error.errno != errno.ENOENT:
54+
raise
55+
56+
request = urllib.Request(VALIDATION_SPEC, headers=headers)
57+
response = urllib.urlopen(request)
58+
59+
if response.code == 200:
60+
with open(spec_path, "w+b") as spec:
61+
spec.writelines(response)
62+
spec.seek(0)
63+
return html.parse(spec)
64+
65+
with open(spec_path) as spec:
66+
return html.parse(spec)
67+
68+
69+
def docutils_sucks(spec):
70+
"""
71+
Yeah.
72+
73+
It doesn't allow using a class because it does stupid stuff like try to set
74+
attributes on the callable object rather than just keeping a dict.
75+
76+
"""
77+
78+
base_url = VALIDATION_SPEC
79+
ref_url = "http://json-schema.org/latest/json-schema-core.html#anchor25"
80+
schema_url = "http://json-schema.org/latest/json-schema-core.html#anchor22"
81+
82+
def validator(name, raw_text, text, lineno, inliner):
83+
"""
84+
Link to the JSON Schema documentation for a validator.
85+
86+
:argument str name: the name of the role in the document
87+
:argument str raw_source: the raw text (role with argument)
88+
:argument str text: the argument given to the role
89+
:argument int lineno: the line number
90+
:argument docutils.parsers.rst.states.Inliner inliner: the inliner
91+
92+
:returns: 2-tuple of nodes to insert into the document and an iterable
93+
of system messages, both possibly empty
94+
95+
"""
96+
97+
if text == "$ref":
98+
return [nodes.reference(raw_text, text, refuri=ref_url)], []
99+
elif text == "$schema":
100+
return [nodes.reference(raw_text, text, refuri=schema_url)], []
101+
102+
# find the header in the validation spec containing matching text
103+
header = spec.xpath("//h1[contains(text(), '{0}')]".format(text))
104+
105+
if len(header) == 0:
106+
inliner.reporter.warning(
107+
"Didn't find a target for {0}".format(text),
108+
)
109+
uri = base_url
110+
else:
111+
if len(header) > 1:
112+
inliner.reporter.info(
113+
"Found multiple targets for {0}".format(text),
114+
)
115+
116+
# get the href from link in the header
117+
uri = base_url + header[0].find('a').attrib["href"]
118+
119+
reference = nodes.reference(raw_text, text, refuri=uri)
120+
return [reference], []
121+
122+
return validator

0 commit comments

Comments
 (0)