Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.

Commit c8c0b54

Browse files
committed
Remove the custom RTD builders
- Uses default Sphinx builders instead (except for zipped media) - Use a Sphinx extension and events to add additional JS/CSS files - We should probably increment a major version for releasing this
1 parent 577a941 commit c8c0b54

File tree

6 files changed

+93
-270
lines changed

6 files changed

+93
-270
lines changed

readthedocs_ext/_static/readthedocs-data.js_t

Lines changed: 0 additions & 25 deletions
This file was deleted.

readthedocs_ext/_templates/readthedocs-insert.html.tmpl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ http://docs.readthedocs.org/en/latest/canonical.html
1818

1919
<link rel="stylesheet" href="{{ rtd_css_url }}" type="text/css" />
2020

21-
<script type="text/javascript" src="{{ pathto('_static/readthedocs-data.js', 1) }}"></script>
21+
<script type="application/json" id="READTHEDOCS_DATA">{{ rtd_data | tojson }}</script>
2222

23-
<!-- Add page-specific data, which must exist in the page js, not global -->
23+
<!--
24+
Using this variable directly instead of using `JSON.parse` is deprecated.
25+
The READTHEDOCS_DATA global variable will be removed in the future.
26+
-->
2427
<script type="text/javascript">
25-
READTHEDOCS_DATA['page'] = {{ pagename|tojson }}
26-
{%- if page_source_suffix %}
27-
READTHEDOCS_DATA['source_suffix'] = {{ page_source_suffix|tojson }}
28-
{%- endif %}
28+
READTHEDOCS_DATA = JSON.parse(document.getElementById('READTHEDOCS_DATA').innerHTML);
2929
</script>
3030

3131
<script type="text/javascript" src="{{ rtd_analytics_url }}" async="async"></script>
32+
<script type="text/javascript" src="{{ rtd_js_url }}" async="async"></script>
3233

3334
<!-- end RTD <extrahead> -->

readthedocs_ext/mixins.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

readthedocs_ext/readthedocs.py

Lines changed: 66 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616

1717
from .embed import EmbedDirective
18-
from .mixins import BuilderMixin
1918

2019
try:
2120
# Avaliable from Sphinx 1.6
@@ -25,26 +24,25 @@
2524

2625
try:
2726
# Available from Sphinx 2.0
28-
from sphinx.builders.dirhtml import DirectoryHTMLBuilder
29-
from sphinx.builders.html import StandaloneHTMLBuilder
3027
from sphinx.builders.singlehtml import SingleFileHTMLBuilder
3128
except ImportError:
32-
from sphinx.builders.html import (DirectoryHTMLBuilder,
33-
SingleFileHTMLBuilder,
34-
StandaloneHTMLBuilder)
29+
from sphinx.builders.html import SingleFileHTMLBuilder
3530

3631
log = getLogger(__name__)
3732

33+
3834
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"
3938
ONLINE_BUILDERS = [
40-
'readthedocs', 'readthedocsdirhtml', 'readthedocssinglehtml'
39+
'html', 'dirhtml', 'singlehtml'
4140
]
4241
# Only run JSON output once during HTML build
4342
# This saves resources and keeps filepaths correct,
4443
# because singlehtml filepaths are different
4544
JSON_BUILDERS = [
4645
'html', 'dirhtml',
47-
'readthedocs', 'readthedocsdirhtml'
4846
]
4947

5048
# Whitelist keys that we want to output
@@ -59,24 +57,6 @@
5957
]
6058

6159

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-
8060
def update_body(app, pagename, templatename, context, doctree):
8161
"""
8262
Add Read the Docs content to Sphinx body content.
@@ -132,18 +112,41 @@ def rtd_render(self, template, render_context):
132112
then adds the Read the Docs HTML content at the end of body.
133113
"""
134114
# 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)
140143
source = os.path.join(
141144
os.path.abspath(os.path.dirname(__file__)),
142145
'_templates',
143146
'readthedocs-insert.html.tmpl'
144147
)
145148
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)
147150

148151
# Handle original render function
149152
content = old_render(template, render_context)
@@ -200,6 +203,34 @@ def generate_json_artifacts(app, pagename, templatename, context, doctree):
200203
)
201204

202205

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+
203234
def dump_sphinx_data(app, exception):
204235
"""
205236
Dump data that is only in memory during Sphinx build.
@@ -261,99 +292,18 @@ def dump_sphinx_data(app, exception):
261292
)
262293

263294

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):
343296

297+
"""Sphinx builder that builds a single HTML file that will be zipped by Read the Docs."""
344298

345-
class ReadtheDocsSingleFileHTMLBuilderLocalMedia(BuilderMixin, SingleFileHTMLBuilder):
346299
name = 'readthedocssinglehtmllocalmedia'
347300

348301

349302
def setup(app):
350-
app.add_builder(ReadtheDocsBuilder)
351-
app.add_builder(ReadtheDocsDirectoryHTMLBuilder)
352-
app.add_builder(ReadtheDocsSingleFileHTMLBuilder)
353303
app.add_builder(ReadtheDocsSingleFileHTMLBuilderLocalMedia)
354-
app.connect('builder-inited', finalize_media)
355304
app.connect('html-page-context', update_body)
356305
app.connect('html-page-context', generate_json_artifacts)
306+
app.connect('build-finished', remove_search_init)
357307
app.connect('build-finished', dump_sphinx_data)
358308

359309
# Embed

0 commit comments

Comments
 (0)