-
Notifications
You must be signed in to change notification settings - Fork 14.2k
/
sphinx_script_update.py
124 lines (100 loc) · 4.23 KB
/
sphinx_script_update.py
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
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import hashlib
import json
import os
import shutil
import sys
import tempfile
from functools import lru_cache
import requests
from sphinx.builders import html as builders
from sphinx.util import logging
log = logging.getLogger(__name__)
def _copy_file(src: str, dst: str) -> None:
log.info("Copying %s -> %s", src, dst)
shutil.copy2(src, dst, follow_symlinks=False)
def _gethash(string: str):
hash_object = hashlib.sha256(string.encode())
return hash_object.hexdigest()
def _user_cache_dir(appname=None):
"""Return full path to the user-specific cache dir for this application"""
if sys.platform == "win32":
# Windows has a complex procedure to download the App Dir directory because this directory can be
# changed in window registry, so i use temporary directory for cache
path = os.path.join(tempfile.gettempdir(), appname)
elif sys.platform == "darwin":
path = os.path.expanduser("~/Library/Caches")
else:
path = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
path = os.path.join(path, appname)
return path
@lru_cache(maxsize=None)
def fetch_and_cache(script_url: str, output_filename: str):
"""Fetch URL to local cache and returns path."""
cache_key = _gethash(script_url)
cache_dir = _user_cache_dir("redoc-doc")
cache_metadata_filepath = os.path.join(cache_dir, "cache-metadata.json")
cache_filepath = os.path.join(cache_dir, f"{cache_key}-{output_filename}")
# Create cache directory
os.makedirs(cache_dir, exist_ok=True)
# Load cache metadata
cache_metadata: dict[str, str] = {}
if os.path.exists(cache_metadata_filepath):
try:
with open(cache_metadata_filepath) as cache_file:
cache_metadata = json.load(cache_file)
except json.JSONDecodeError:
os.remove(cache_metadata_filepath)
etag = cache_metadata.get(cache_key)
# If we have a file and etag, check the fast path
if os.path.exists(cache_filepath) and etag:
res = requests.get(script_url, headers={"If-None-Match": etag})
if res.status_code == 304:
return cache_filepath
# Slow patch
res = requests.get(script_url)
res.raise_for_status()
with open(cache_filepath, "wb") as output_file:
output_file.write(res.content)
# Save cache metadata, if needed
etag = res.headers.get("etag", None)
if etag:
cache_metadata[cache_key] = etag
with open(cache_metadata_filepath, "w") as cache_file:
json.dump(cache_metadata, cache_file)
return cache_filepath
def builder_inited(app):
"""Sphinx "builder-inited" event handler."""
script_url = app.config.redoc_script_url
output_filename = "script.js"
fetch_and_cache(script_url, output_filename)
def build_finished(app, exception):
"""Sphinx "build-finished" event handler."""
if exception or not isinstance(app.builder, builders.StandaloneHTMLBuilder):
return
script_url = app.config.redoc_script_url
output_filename = "script.js"
cache_filepath = fetch_and_cache(script_url, output_filename)
_copy_file(cache_filepath, os.path.join(app.builder.outdir, "_static", "redoc.js"))
def setup(app):
"""Setup plugin"""
app.add_config_value("redoc_script_url", None, "env")
app.connect("builder-inited", builder_inited)
app.connect("build-finished", build_finished)
return {"parallel_read_safe": True, "parallel_write_safe": True}