124 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from datetime import datetime
 | 
						|
from docutils import nodes
 | 
						|
import errno
 | 
						|
import os
 | 
						|
 | 
						|
try:
 | 
						|
    import urllib2 as urllib
 | 
						|
except ImportError:
 | 
						|
    import urllib.request as urllib
 | 
						|
 | 
						|
from lxml import html
 | 
						|
 | 
						|
 | 
						|
VALIDATION_SPEC = "http://json-schema.org/latest/json-schema-validation.html"
 | 
						|
 | 
						|
 | 
						|
def setup(app):
 | 
						|
    """
 | 
						|
    Install the plugin.
 | 
						|
 | 
						|
    :argument sphinx.application.Sphinx app: 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_sucks(spec))
 | 
						|
 | 
						|
 | 
						|
def fetch_or_load(spec_path):
 | 
						|
    """
 | 
						|
    Fetch a new specification or use the cache if it's current.
 | 
						|
 | 
						|
    :argument cache_path: the path to a cached specification
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    headers = {}
 | 
						|
 | 
						|
    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(VALIDATION_SPEC, headers=headers)
 | 
						|
    response = urllib.urlopen(request)
 | 
						|
 | 
						|
    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_sucks(spec):
 | 
						|
    """
 | 
						|
    Yeah.
 | 
						|
 | 
						|
    It doesn't allow using a class because it does stupid stuff like try to set
 | 
						|
    attributes on the callable object rather than just keeping a dict.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    base_url = VALIDATION_SPEC
 | 
						|
    ref_url = "http://json-schema.org/latest/json-schema-core.html#anchor25"
 | 
						|
    schema_url = "http://json-schema.org/latest/json-schema-core.html#anchor22"
 | 
						|
 | 
						|
    def validator(name, raw_text, text, lineno, inliner):
 | 
						|
        """
 | 
						|
        Link to the JSON Schema documentation for a validator.
 | 
						|
 | 
						|
        :argument str name: the name of the role in the document
 | 
						|
        :argument str raw_source: the raw text (role with argument)
 | 
						|
        :argument str text: the argument given to the role
 | 
						|
        :argument int lineno: the line number
 | 
						|
        :argument docutils.parsers.rst.states.Inliner inliner: the inliner
 | 
						|
 | 
						|
        :returns: 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)], []
 | 
						|
 | 
						|
        xpath = "//h3[re:match(text(), '(^|\W)\"?{0}\"?($|\W,)', 'i')]"
 | 
						|
        header = spec.xpath(
 | 
						|
            xpath.format(text),
 | 
						|
            namespaces={"re": "http://exslt.org/regular-expressions"},
 | 
						|
        )
 | 
						|
 | 
						|
        if len(header) == 0:
 | 
						|
            inliner.reporter.warning(
 | 
						|
                "Didn't find a target for {0}".format(text),
 | 
						|
            )
 | 
						|
            uri = base_url
 | 
						|
        else:
 | 
						|
            if len(header) > 1:
 | 
						|
                inliner.reporter.info(
 | 
						|
                    "Found multiple targets for {0}".format(text),
 | 
						|
                )
 | 
						|
            uri = base_url + "#" + header[0].getprevious().attrib["name"]
 | 
						|
 | 
						|
        reference = nodes.reference(raw_text, text, refuri=uri)
 | 
						|
        return [reference], []
 | 
						|
 | 
						|
    return validator
 |