|
15 | 15 |
|
16 | 16 |
|
17 | 17 | from .embed import EmbedDirective
|
18 |
| -from .mixins import BuilderMixin |
19 | 18 |
|
20 | 19 | try:
|
21 | 20 | # Avaliable from Sphinx 1.6
|
|
25 | 24 |
|
26 | 25 | try:
|
27 | 26 | # Available from Sphinx 2.0
|
28 |
| - from sphinx.builders.dirhtml import DirectoryHTMLBuilder |
29 |
| - from sphinx.builders.html import StandaloneHTMLBuilder |
30 | 27 | from sphinx.builders.singlehtml import SingleFileHTMLBuilder
|
31 | 28 | except ImportError:
|
32 |
| - from sphinx.builders.html import (DirectoryHTMLBuilder, |
33 |
| - SingleFileHTMLBuilder, |
34 |
| - StandaloneHTMLBuilder) |
| 29 | + from sphinx.builders.html import SingleFileHTMLBuilder |
35 | 30 |
|
36 | 31 | log = getLogger(__name__)
|
37 | 32 |
|
| 33 | + |
38 | 34 | DEFAULT_STATIC_URL = 'https://assets.readthedocs.org/static/'
|
| 35 | + |
| 36 | +# Exclude the SingleHTML builder that is used by RTD to zip up local media |
| 37 | +# That builder is never used "online" |
39 | 38 | ONLINE_BUILDERS = [
|
40 |
| - 'readthedocs', 'readthedocsdirhtml', 'readthedocssinglehtml' |
| 39 | + 'html', 'dirhtml', 'singlehtml' |
41 | 40 | ]
|
42 | 41 | # Only run JSON output once during HTML build
|
43 | 42 | # This saves resources and keeps filepaths correct,
|
44 | 43 | # because singlehtml filepaths are different
|
45 | 44 | JSON_BUILDERS = [
|
46 | 45 | 'html', 'dirhtml',
|
47 |
| - 'readthedocs', 'readthedocsdirhtml' |
48 | 46 | ]
|
49 | 47 |
|
50 | 48 | # Whitelist keys that we want to output
|
|
59 | 57 | ]
|
60 | 58 |
|
61 | 59 |
|
62 |
| -def finalize_media(app): |
63 |
| - """Point media files at our media server.""" |
64 |
| - |
65 |
| - if (app.builder.name == 'readthedocssinglehtmllocalmedia' or |
66 |
| - app.builder.format != 'html' or |
67 |
| - not hasattr(app.builder, 'script_files')): |
68 |
| - return # Use local media for downloadable files |
69 |
| - # Pull project data from conf.py if it exists |
70 |
| - context = app.builder.config.html_context |
71 |
| - STATIC_URL = context.get('STATIC_URL', DEFAULT_STATIC_URL) |
72 |
| - js_file = '{}javascript/readthedocs-doc-embed.js'.format(STATIC_URL) |
73 |
| - if sphinx.version_info < (1, 8): |
74 |
| - app.builder.script_files.append(js_file) |
75 |
| - else: |
76 |
| - kwargs = {'async': 'async'} # Workaround reserved word in Py3.7 |
77 |
| - app.add_js_file(js_file, **kwargs) |
78 |
| - |
79 |
| - |
80 | 60 | def update_body(app, pagename, templatename, context, doctree):
|
81 | 61 | """
|
82 | 62 | Add Read the Docs content to Sphinx body content.
|
@@ -132,18 +112,41 @@ def rtd_render(self, template, render_context):
|
132 | 112 | then adds the Read the Docs HTML content at the end of body.
|
133 | 113 | """
|
134 | 114 | # Render Read the Docs content
|
135 |
| - template_context = render_context.copy() |
136 |
| - template_context['rtd_css_url'] = '{}css/readthedocs-doc-embed.css'.format(STATIC_URL) |
137 |
| - template_context['rtd_analytics_url'] = '{}javascript/readthedocs-analytics.js'.format( |
138 |
| - STATIC_URL, |
139 |
| - ) |
| 115 | + ctx = render_context.copy() |
| 116 | + ctx['rtd_data'] = { |
| 117 | + 'project': ctx.get('slug', ''), |
| 118 | + 'version': ctx.get('version_slug', ''), |
| 119 | + 'language': ctx.get('rtd_language', ''), |
| 120 | + 'programming_language': ctx.get('programming_language', ''), |
| 121 | + 'canonical_url': ctx.get('canonical_url', ''), |
| 122 | + 'theme': ctx.get('html_theme', ''), |
| 123 | + 'builder': 'sphinx', |
| 124 | + 'docroot': ctx.get('conf_py_path', ''), |
| 125 | + 'source_suffix': ctx.get('source_suffix', ''), |
| 126 | + 'page': ctx.get('pagename', ''), |
| 127 | + 'api_host': ctx.get('api_host', ''), |
| 128 | + 'commit': ctx.get('commit', ''), |
| 129 | + 'ad_free': ctx.get('ad_free', ''), |
| 130 | + 'global_analytics_code': ctx.get('global_analytics_code'), |
| 131 | + 'user_analytics_code': ctx.get('user_analytics_code'), |
| 132 | + 'subprojects': { |
| 133 | + slug: url for slug, url in ctx.get('subprojects', []) |
| 134 | + }, |
| 135 | + } |
| 136 | + if ctx.get('page_source_suffix'): |
| 137 | + ctx['rtd_data']['source_suffix'] = ctx['page_source_suffix'] |
| 138 | + if ctx.get('proxied_api_host'): |
| 139 | + ctx['rtd_data']['proxied_api_host'] = ctx['proxied_api_host'] |
| 140 | + ctx['rtd_css_url'] = '{}css/readthedocs-doc-embed.css'.format(STATIC_URL) |
| 141 | + ctx['rtd_js_url'] = '{}javascript/readthedocs-doc-embed.js'.format(STATIC_URL) |
| 142 | + ctx['rtd_analytics_url'] = '{}javascript/readthedocs-analytics.js'.format(STATIC_URL) |
140 | 143 | source = os.path.join(
|
141 | 144 | os.path.abspath(os.path.dirname(__file__)),
|
142 | 145 | '_templates',
|
143 | 146 | 'readthedocs-insert.html.tmpl'
|
144 | 147 | )
|
145 | 148 | templ = open(source).read()
|
146 |
| - rtd_content = app.builder.templates.render_string(templ, template_context) |
| 149 | + rtd_content = app.builder.templates.render_string(templ, ctx) |
147 | 150 |
|
148 | 151 | # Handle original render function
|
149 | 152 | content = old_render(template, render_context)
|
@@ -200,6 +203,34 @@ def generate_json_artifacts(app, pagename, templatename, context, doctree):
|
200 | 203 | )
|
201 | 204 |
|
202 | 205 |
|
| 206 | +def remove_search_init(app, exception): |
| 207 | + """Remove Sphinx's Search.init() so it can be initialized by Read the Docs.""" |
| 208 | + if exception: |
| 209 | + return |
| 210 | + |
| 211 | + searchtools_file = os.path.abspath( |
| 212 | + os.path.join(app.outdir, '_static', 'searchtools.js') |
| 213 | + ) |
| 214 | + |
| 215 | + if os.path.exists(searchtools_file): |
| 216 | + replacement_text = '/* Search initialization removed for Read the Docs */' |
| 217 | + replacement_regex = re.compile( |
| 218 | + r''' |
| 219 | + ^\$\(document\).ready\(function\s*\(\)\s*{(?:\n|\r\n?) |
| 220 | + \s*Search.init\(\);(?:\n|\r\n?) |
| 221 | + \}\); |
| 222 | + ''', |
| 223 | + (re.MULTILINE | re.VERBOSE) |
| 224 | + ) |
| 225 | + |
| 226 | + log.info(bold('Updating searchtools for Read the Docs search... '), nonl=True) |
| 227 | + with codecs.open(searchtools_file, 'r', encoding='utf-8') as infile: |
| 228 | + data = infile.read() |
| 229 | + with codecs.open(searchtools_file, 'w', encoding='utf-8') as outfile: |
| 230 | + data = replacement_regex.sub(replacement_text, data) |
| 231 | + outfile.write(data) |
| 232 | + |
| 233 | + |
203 | 234 | def dump_sphinx_data(app, exception):
|
204 | 235 | """
|
205 | 236 | Dump data that is only in memory during Sphinx build.
|
@@ -261,99 +292,18 @@ def dump_sphinx_data(app, exception):
|
261 | 292 | )
|
262 | 293 |
|
263 | 294 |
|
264 |
| -class HtmlBuilderMixin(BuilderMixin): |
265 |
| - |
266 |
| - static_readthedocs_files = [ |
267 |
| - 'readthedocs-data.js_t', |
268 |
| - # We patch searchtools and copy it with a special handler |
269 |
| - # 'searchtools.js_t' |
270 |
| - ] |
271 |
| - |
272 |
| - REPLACEMENT_TEXT = '/* Search initialization removed for Read the Docs */' |
273 |
| - REPLACEMENT_PATTERN = re.compile( |
274 |
| - r''' |
275 |
| - ^\$\(document\).ready\(function\s*\(\)\s*{(?:\n|\r\n?) |
276 |
| - \s*Search.init\(\);(?:\n|\r\n?) |
277 |
| - \}\); |
278 |
| - ''', |
279 |
| - (re.MULTILINE | re.VERBOSE) |
280 |
| - ) |
281 |
| - |
282 |
| - def get_static_readthedocs_context(self): |
283 |
| - ctx = super(HtmlBuilderMixin, self).get_static_readthedocs_context() |
284 |
| - if self.indexer is not None: |
285 |
| - ctx.update(self.indexer.context_for_searchtool()) |
286 |
| - return ctx |
287 |
| - |
288 |
| - def copy_static_readthedocs_files(self): |
289 |
| - super(HtmlBuilderMixin, self).copy_static_readthedocs_files() |
290 |
| - self._copy_searchtools() |
291 |
| - |
292 |
| - def _copy_searchtools(self, renderer=None): |
293 |
| - """Copy and patch searchtools |
294 |
| -
|
295 |
| - This uses the included Sphinx version's searchtools, but patches it to |
296 |
| - remove automatic initialization. This is a fork of |
297 |
| - ``sphinx.util.fileutil.copy_asset`` |
298 |
| - """ |
299 |
| - log.info(bold('copying searchtools... '), nonl=True) |
300 |
| - |
301 |
| - if sphinx.version_info < (1, 8): |
302 |
| - search_js_file = 'searchtools.js_t' |
303 |
| - else: |
304 |
| - search_js_file = 'searchtools.js' |
305 |
| - path_src = os.path.join( |
306 |
| - package_dir, 'themes', 'basic', 'static', search_js_file |
307 |
| - ) |
308 |
| - if os.path.exists(path_src): |
309 |
| - path_dest = os.path.join(self.outdir, '_static', 'searchtools.js') |
310 |
| - if renderer is None: |
311 |
| - # Sphinx 1.4 used the renderer from the existing builder, but |
312 |
| - # the pattern for Sphinx 1.5 is to pass in a renderer separate |
313 |
| - # from the builder. This supports both patterns for future |
314 |
| - # compatibility |
315 |
| - if sphinx.version_info < (1, 5): |
316 |
| - renderer = self.templates |
317 |
| - else: |
318 |
| - from sphinx.util.template import SphinxRenderer |
319 |
| - renderer = SphinxRenderer() |
320 |
| - with codecs.open(path_src, 'r', encoding='utf-8') as h_src: |
321 |
| - with codecs.open(path_dest, 'w', encoding='utf-8') as h_dest: |
322 |
| - data = h_src.read() |
323 |
| - data = self.REPLACEMENT_PATTERN.sub(self.REPLACEMENT_TEXT, data) |
324 |
| - h_dest.write(renderer.render_string( |
325 |
| - data, |
326 |
| - self.get_static_readthedocs_context() |
327 |
| - )) |
328 |
| - else: |
329 |
| - log.warning('Missing {}'.format(search_js_file)) |
330 |
| - log.info('done') |
331 |
| - |
332 |
| - |
333 |
| -class ReadtheDocsBuilder(HtmlBuilderMixin, StandaloneHTMLBuilder): |
334 |
| - name = 'readthedocs' |
335 |
| - |
336 |
| - |
337 |
| -class ReadtheDocsDirectoryHTMLBuilder(HtmlBuilderMixin, DirectoryHTMLBuilder): |
338 |
| - name = 'readthedocsdirhtml' |
339 |
| - |
340 |
| - |
341 |
| -class ReadtheDocsSingleFileHTMLBuilder(BuilderMixin, SingleFileHTMLBuilder): |
342 |
| - name = 'readthedocssinglehtml' |
| 295 | +class ReadtheDocsSingleFileHTMLBuilderLocalMedia(SingleFileHTMLBuilder): |
343 | 296 |
|
| 297 | + """Sphinx builder that builds a single HTML file that will be zipped by Read the Docs.""" |
344 | 298 |
|
345 |
| -class ReadtheDocsSingleFileHTMLBuilderLocalMedia(BuilderMixin, SingleFileHTMLBuilder): |
346 | 299 | name = 'readthedocssinglehtmllocalmedia'
|
347 | 300 |
|
348 | 301 |
|
349 | 302 | def setup(app):
|
350 |
| - app.add_builder(ReadtheDocsBuilder) |
351 |
| - app.add_builder(ReadtheDocsDirectoryHTMLBuilder) |
352 |
| - app.add_builder(ReadtheDocsSingleFileHTMLBuilder) |
353 | 303 | app.add_builder(ReadtheDocsSingleFileHTMLBuilderLocalMedia)
|
354 |
| - app.connect('builder-inited', finalize_media) |
355 | 304 | app.connect('html-page-context', update_body)
|
356 | 305 | app.connect('html-page-context', generate_json_artifacts)
|
| 306 | + app.connect('build-finished', remove_search_init) |
357 | 307 | app.connect('build-finished', dump_sphinx_data)
|
358 | 308 |
|
359 | 309 | # Embed
|
|
0 commit comments