Skip to content

Commit 8224381

Browse files
committed
Add support for linking to JSON Schema Glossary entries.
1 parent be165b5 commit 8224381

File tree

2 files changed

+61
-15
lines changed

2 files changed

+61
-15
lines changed

docs/index.rst

+16-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ The extension currently provides a single Sphinx `role`:
99

1010
.. rst:role:: kw
1111
12-
Link to the current JSON Schema specification's definition of the keyword
13-
provided.
12+
Link to the current JSON Schema specification's definition of the keyword provided.
1413

1514
For instance, writing:
1615

@@ -22,7 +21,21 @@ will produce:
2221

2322
Reference resolution in JSON Schema is done using the :kw:`$ref` keyword.
2423

24+
In addition, the extension automatically populates the Sphinx glossary with terms from the `JSON Schema Glossary <https://json-schema.org/learn/glossary.html>`_, such that:
25+
26+
.. code-block:: rst
27+
28+
If a :term:`schema` has a :term:`meta-schema`, what do :term:`meta-schemas <meta-schema>` have?
29+
30+
will produce:
31+
32+
If a :term:`schema` has a :term:`meta-schema`, what do :term:`meta-schemas <meta-schema>` have?
33+
34+
35+
Contributing
36+
------------
37+
2538
What's here, albeit crude, has been used in some form for a long while by `jsonschema` (the Python library), but the hope is it may be useful to alternate implementations or users in general.
2639
Help is very much welcome to improve it!
2740

28-
In the future support may be added for linking to different drafts, or for linking to `Understanding JSON Schema <https://json-schema.org/understanding-json-schema/index.html>`_.
41+
In particular, help adding support for other (historic or future) drafts, rather than just the single draft currently supported, would be great.

sphinx_json_schema_spec/__init__.py

+45-12
Original file line numberDiff line numberDiff line change
@@ -42,47 +42,53 @@ def setup(app):
4242
CACHE.mkdir(exist_ok=True)
4343

4444
documents = {
45-
url: fetch_or_load(vocabulary_path=CACHE / f"{name}.html", url=url)
45+
url: fetch_or_load(cache_path=CACHE / f"{name}.html", url=url)
4646
for name, url in VOCABULARIES.items()
4747
}
4848
app.add_role("kw", docutils_does_not_allow_using_classes(documents))
4949

50+
glossary = fetch_or_load(
51+
cache_path=CACHE / "glossary.html",
52+
url="https://json-schema.org/learn/glossary.html",
53+
)
54+
app.connect("missing-reference", missing_reference(glossary))
55+
5056
return dict(parallel_read_safe=True)
5157

5258

53-
def fetch_or_load(vocabulary_path, url):
59+
def fetch_or_load(cache_path, url):
5460
"""
55-
Fetch a new specification or use the cache if it's current.
61+
Fetch a new page or specification, or use the cache if it's current.
5662
5763
Arguments:
5864
59-
vocabulary_path:
65+
cache_path:
6066
61-
the local path to a cached vocabulary document
67+
the local path to a (possibly not yet existing) cache for the file
6268
6369
url:
6470
65-
the URL of the vocabulary document
71+
the URL of the document
6672
"""
6773

6874
version = metadata.version("sphinx-json-schema-spec")
6975
headers = {"User-Agent": f"sphinx-json-schema-spec v{version}"}
7076

7177
with suppress(FileNotFoundError):
72-
modified = datetime.utcfromtimestamp(vocabulary_path.stat().st_mtime)
78+
modified = datetime.utcfromtimestamp(cache_path.stat().st_mtime)
7379
date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC")
7480
headers["If-Modified-Since"] = date
7581

7682
request = urllib.request.Request(url, headers=headers)
7783
response = urllib.request.urlopen(request, cafile=certifi.where())
7884

7985
if response.code == 200:
80-
with vocabulary_path.open("w+b") as spec:
81-
spec.writelines(response)
82-
spec.seek(0)
83-
return html.parse(spec).getroot()
86+
with cache_path.open("w+b") as out:
87+
out.writelines(response)
88+
out.seek(0)
89+
return html.parse(out).getroot()
8490

85-
return html.parse(vocabulary_path.read_bytes()).getroot()
91+
return html.parse(cache_path.read_bytes()).getroot()
8692

8793

8894
def docutils_does_not_allow_using_classes(vocabularies):
@@ -149,3 +155,30 @@ def keyword(name, raw_text, text, lineno, inliner):
149155
return [reference], []
150156

151157
return keyword
158+
159+
160+
def missing_reference(glossary):
161+
162+
terms = {
163+
link.lstrip("#")
164+
for _, _, link, _ in glossary.iterlinks()
165+
if link.startswith("#")
166+
}
167+
168+
def _missing_reference(app, env, node, contnod):
169+
"""
170+
Resolve a reference to a JSON Schema Glossary term.
171+
"""
172+
173+
if node["reftype"] != "term":
174+
return
175+
176+
target = node["reftarget"]
177+
if target not in terms:
178+
return
179+
180+
uri = f"https://json-schema.org/learn/glossary.html#{target}"
181+
182+
text = contnod.astext() if node["refexplicit"] else target
183+
return nodes.reference(text, text, internal=False, refuri=uri)
184+
return _missing_reference

0 commit comments

Comments
 (0)