diff --git a/.travis.yml b/.travis.yml
index a56d9f6..0bfc4ef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,8 @@ python:
sudo: false
env:
- TOX_ENV=lint
+ - TOX_ENV=py27
+ - TOX_ENV=py34
install:
- pip install tox
script:
diff --git a/MANIFEST.in b/MANIFEST.in
index a037f16..0f931dd 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,5 @@
recursive-include readthedocs_ext *.css
recursive-include readthedocs_ext *.js_t
recursive-include readthedocs_ext *.js
+recursive-include readthedocs_ext *.tmpl
include *.rst
diff --git a/readthedocs_ext/_static/readthedocs-data.js_t b/readthedocs_ext/_static/readthedocs-data.js_t
index b7e56ef..e5bebfa 100644
--- a/readthedocs_ext/_static/readthedocs-data.js_t
+++ b/readthedocs_ext/_static/readthedocs-data.js_t
@@ -2,7 +2,6 @@
project: "{{ slug }}",
version: "{{ current_version }}",
language: "{{ rtd_language }}",
- page: "{{ pagename }}",
theme: "{{ html_theme }}",
builder: "sphinx",
docroot: "{{ conf_py_path }}",
@@ -10,9 +9,3 @@
api_host: "{{ api_host }}",
commit: "{{ commit }}"
}
-
- // Old variables
- var doc_version = "{{ current_version }}";
- var doc_slug = "{{ slug }}";
- var page_name = "{{ pagename }}";
- var html_theme = "{{ html_theme }}";
\ No newline at end of file
diff --git a/readthedocs_ext/_templates/readthedocs-insert.html.tmpl b/readthedocs_ext/_templates/readthedocs-insert.html.tmpl
new file mode 100644
index 0000000..48fb501
--- /dev/null
+++ b/readthedocs_ext/_templates/readthedocs-insert.html.tmpl
@@ -0,0 +1,34 @@
+
+
+
+{%- if pagename == "index" %}
+ {%- set canonical_page = "" %}
+{%- elif pagename.endswith("/index") %}
+ {%- set canonical_page = pagename[:-("/index"|length)] + "/" %}
+{%- else %}
+ {%- set ending = "/" if builder == "readthedocsdirhtml" else ".html" %}
+ {%- set canonical_page = pagename + ending %}
+{%- endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readthedocs_ext/comments/builder.py b/readthedocs_ext/comments/builder.py
new file mode 100644
index 0000000..f390017
--- /dev/null
+++ b/readthedocs_ext/comments/builder.py
@@ -0,0 +1,70 @@
+from __future__ import absolute_import
+
+from collections import defaultdict
+
+from sphinx.builders.html import StandaloneHTMLBuilder, DirectoryHTMLBuilder
+
+from . import backend, translator
+
+
+def finalize_comment_media(app):
+
+ if 'comments' not in app.builder.name:
+ return
+ builder = app.builder
+ # Pull project data from conf.py if it exists
+ builder.storage = backend.WebStorage(builder=builder)
+ builder.page_hash_mapping = defaultdict(list)
+ builder.metadata_mapping = defaultdict(list)
+ try:
+ builder.comment_metadata = builder.storage.get_project_metadata(
+ builder.config.html_context['slug'])['results']
+ for obj in builder.comment_metadata:
+ builder.metadata_mapping[obj['node']['page']].append(obj['node'])
+ except:
+ builder.comment_metadata = {}
+
+ context = builder.config.html_context
+ MEDIA_URL = context.get('MEDIA_URL', 'https://media.readthedocs.org/')
+
+ # add our custom bits
+ builder.script_files.append('_static/jquery.pageslide.js')
+ # builder.script_files.append('_static/websupport2-bundle.js')
+ builder.script_files.append(
+ '%sjavascript/websupport2-bundle.js' % MEDIA_URL)
+ builder.css_files.append('_static/websupport2.css')
+ builder.css_files.append('_static/sphinxweb.css')
+ builder.css_files.append('_static/jquery.pageslide.css')
+
+
+class ReadtheDocsBuilderComments(StandaloneHTMLBuilder):
+
+ """
+ Comment Builders.
+
+ Sets the translator class,
+ which handles adding a content-specific hash to each text node object.
+ """
+ name = 'readthedocs-comments'
+ versioning_method = 'commentable'
+
+ def init(self):
+ StandaloneHTMLBuilder.init(self)
+ finalize_comment_media(self)
+
+ def init_translator_class(self):
+ self.translator_class = translator.UUIDTranslator
+
+
+class ReadtheDocsDirectoryHTMLBuilderComments(DirectoryHTMLBuilder):
+
+ """ Adds specific media files to script_files and css_files. """
+ name = 'readthedocsdirhtml-comments'
+ versioning_method = 'commentable'
+
+ def init(self):
+ DirectoryHTMLBuilder.init(self)
+ finalize_comment_media(self)
+
+ def init_translator_class(self):
+ self.translator_class = translator.UUIDTranslator
diff --git a/readthedocs_ext/readthedocs.py b/readthedocs_ext/readthedocs.py
index 6d06b24..d8dc06c 100644
--- a/readthedocs_ext/readthedocs.py
+++ b/readthedocs_ext/readthedocs.py
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
import os
-from collections import defaultdict
+import types
from sphinx.builders.html import StandaloneHTMLBuilder, DirectoryHTMLBuilder, SingleFileHTMLBuilder
from sphinx.util import copy_static_entry
from sphinx.util.console import bold
-from .comments import backend, translator, directive
+from .comments.builder import (finalize_comment_media, ReadtheDocsBuilderComments,
+ ReadtheDocsDirectoryHTMLBuilderComments)
+from .comments.directive import CommentConfigurationDirective
from .embed import EmbedDirective
MEDIA_MAPPING = {
@@ -22,8 +26,95 @@
]
+def finalize_media(app):
+ """ Point media files at our media server. """
+
+ if (app.builder.name == 'readthedocssinglehtmllocalmedia' or
+ app.builder.format != 'html' or
+ not hasattr(app.builder, 'script_files')):
+ return # Use local media for downloadable files
+ # Pull project data from conf.py if it exists
+ context = app.builder.config.html_context
+ MEDIA_URL = context.get('MEDIA_URL', 'https://media.readthedocs.org/')
+
+ # Put in our media files instead of putting them in the docs.
+ for index, file in enumerate(app.builder.script_files):
+ if file in MEDIA_MAPPING.keys():
+ app.builder.script_files[index] = MEDIA_MAPPING[file] % MEDIA_URL
+ if file == "_static/jquery.js":
+ app.builder.script_files.insert(
+ index + 1, "%sjavascript/jquery/jquery-migrate-1.2.1.min.js" % MEDIA_URL)
+ app.builder.script_files.append(
+ '%sjavascript/readthedocs-doc-embed.js' % MEDIA_URL
+ )
+
+
+def update_body(app, pagename, templatename, context, doctree):
+ """
+ Add Read the Docs content to Sphinx body content.
+
+ This is the most reliable way to inject our content into the page.
+ """
+
+ MEDIA_URL = context.get('MEDIA_URL', 'https://media.readthedocs.org/')
+ if app.builder.name == 'readthedocssinglehtmllocalmedia':
+ if 'html_theme' in context and context['html_theme'] == 'sphinx_rtd_theme':
+ theme_css = '_static/css/theme.css'
+ else:
+ theme_css = '_static/css/badge_only.css'
+ elif app.builder.name in ['readthedocs', 'readthedocsdirhtml']:
+ if 'html_theme' in context and context['html_theme'] == 'sphinx_rtd_theme':
+ theme_css = '%scss/sphinx_rtd_theme.css' % MEDIA_URL
+ else:
+ theme_css = '%scss/badge_only.css' % MEDIA_URL
+ else:
+ # Only insert on our HTML builds
+ return
+
+ # This is monkey patched on the signal because we can't know what the user
+ # has done with their `app.builder.templates` before now.
+
+ if not hasattr(app.builder.templates.render, '_patched'):
+ # Janky monkey patch of template rendering to add our content
+ old_render = app.builder.templates.render
+
+ def rtd_render(self, template, render_context):
+ """
+ A decorator that renders the content with the users template renderer,
+ then adds the Read the Docs HTML content at the end of body.
+ """
+ # Render Read the Docs content
+ template_context = render_context.copy()
+ template_context['theme_css'] = theme_css
+ template_context['rtd_css_url'] = '%scss/readthedocs-doc-embed.css' % MEDIA_URL
+ source = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ '_templates',
+ 'readthedocs-insert.html.tmpl'
+ )
+ templ = open(source).read()
+ rtd_content = app.builder.templates.render_string(templ, template_context)
+
+ # Handle original render function
+ content = old_render(template, render_context)
+ end_body = content.lower().find('')
+
+ # Insert our content at the end of the body.
+ if end_body != -1:
+ content = content[:end_body] + rtd_content + content[end_body:]
+ else:
+ app.debug("File doesn't look like HTML. Skipping RTD content addition")
+
+ return content
+
+ rtd_render._patched = True
+ app.builder.templates.render = types.MethodType(rtd_render,
+ app.builder.templates)
+
+
def copy_media(app, exception):
- if app.builder.name == 'readthedocs' and not exception:
+ """ Move our dynamically generated files after build. """
+ if app.builder.name in ['readthedocs', 'readthedocsdirhtml'] and not exception:
for file in ['readthedocs-dynamic-include.js_t', 'readthedocs-data.js_t',
'searchtools.js_t']:
app.info(bold('Copying %s... ' % file), nonl=True)
@@ -61,69 +152,6 @@ def copy_media(app, exception):
app.info('done')
-def finalize_media(builder, local=False):
- # Pull project data from conf.py if it exists
- context = builder.config.html_context
- MEDIA_URL = context.get('MEDIA_URL', 'https://media.readthedocs.org/')
-
- # Put in our media files instead of putting them in the docs.
- for index, file in enumerate(builder.script_files):
- if file in MEDIA_MAPPING.keys():
- builder.script_files[index] = MEDIA_MAPPING[file] % MEDIA_URL
- if file == "_static/jquery.js":
- builder.script_files.insert(
- index + 1, "%sjavascript/jquery/jquery-migrate-1.2.1.min.js" % MEDIA_URL)
-
- if local:
- if 'html_theme' in context and context['html_theme'] == 'sphinx_rtd_theme':
- builder.css_files.insert(0, '_static/css/theme.css')
- else:
- builder.css_files.insert(0, '_static/css/badge_only.css')
- else:
- if 'html_theme' in context and context['html_theme'] == 'sphinx_rtd_theme':
- builder.css_files.insert(
- 0, '%scss/sphinx_rtd_theme.css' % MEDIA_URL)
- else:
- builder.css_files.insert(0, '%scss/badge_only.css' % MEDIA_URL)
-
- # Analytics codes
- # builder.script_files.append('_static/readthedocs-data.js')
- # builder.script_files.append('_static/readthedocs-dynamic-include.js')
- # We include the media servers version here so we can update rtd.js across all
- # documentation without rebuilding every one.
- # If this script is embedded in each build,
- # then updating the file across all docs is basically impossible.
- builder.script_files.append(
- '%sjavascript/readthedocs-doc-embed.js' % MEDIA_URL)
- builder.css_files.append('%scss/readthedocs-doc-embed.css' % MEDIA_URL)
-
-
-def finalize_comment_media(builder):
- # Pull project data from conf.py if it exists
- builder.storage = backend.WebStorage(builder=builder)
- builder.page_hash_mapping = defaultdict(list)
- builder.metadata_mapping = defaultdict(list)
- try:
- builder.comment_metadata = builder.storage.get_project_metadata(
- builder.config.html_context['slug'])['results']
- for obj in builder.comment_metadata:
- builder.metadata_mapping[obj['node']['page']].append(obj['node'])
- except:
- builder.comment_metadata = {}
-
- context = builder.config.html_context
- MEDIA_URL = context.get('MEDIA_URL', 'https://media.readthedocs.org/')
-
- # add our custom bits
- builder.script_files.append('_static/jquery.pageslide.js')
- # builder.script_files.append('_static/websupport2-bundle.js')
- builder.script_files.append(
- '%sjavascript/websupport2-bundle.js' % MEDIA_URL)
- builder.css_files.append('_static/websupport2.css')
- builder.css_files.append('_static/sphinxweb.css')
- builder.css_files.append('_static/jquery.pageslide.css')
-
-
class ReadtheDocsBuilder(StandaloneHTMLBuilder):
"""
@@ -131,30 +159,6 @@ class ReadtheDocsBuilder(StandaloneHTMLBuilder):
"""
name = 'readthedocs'
- def init(self):
- StandaloneHTMLBuilder.init(self)
- finalize_media(self)
-
-
-class ReadtheDocsBuilderComments(StandaloneHTMLBuilder):
-
- """
- Comment Builders.
-
- Sets the translator class,
- which handles adding a content-specific hash to each text node object.
- """
- name = 'readthedocs-comments'
- versioning_method = 'commentable'
-
- def init(self):
- StandaloneHTMLBuilder.init(self)
- finalize_media(self)
- finalize_comment_media(self)
-
- def init_translator_class(self):
- self.translator_class = translator.UUIDTranslator
-
class ReadtheDocsDirectoryHTMLBuilder(DirectoryHTMLBuilder):
@@ -163,39 +167,13 @@ class ReadtheDocsDirectoryHTMLBuilder(DirectoryHTMLBuilder):
"""
name = 'readthedocsdirhtml'
- def init(self):
- DirectoryHTMLBuilder.init(self)
- finalize_media(self)
-
-
-class ReadtheDocsDirectoryHTMLBuilderComments(DirectoryHTMLBuilder):
-
- """
- Adds specific media files to script_files and css_files.
- """
- name = 'readthedocsdirhtml-comments'
- versioning_method = 'commentable'
-
- def init(self):
- DirectoryHTMLBuilder.init(self)
- finalize_media(self)
- finalize_comment_media(self)
-
- def init_translator_class(self):
- self.translator_class = translator.UUIDTranslator
-
class ReadtheDocsSingleFileHTMLBuilder(SingleFileHTMLBuilder):
-
"""
Adds specific media files to script_files and css_files.
"""
name = 'readthedocssinglehtml'
- def init(self):
- SingleFileHTMLBuilder.init(self)
- finalize_media(self)
-
class ReadtheDocsSingleFileHTMLBuilderLocalMedia(SingleFileHTMLBuilder):
@@ -204,22 +182,21 @@ class ReadtheDocsSingleFileHTMLBuilderLocalMedia(SingleFileHTMLBuilder):
"""
name = 'readthedocssinglehtmllocalmedia'
- def init(self):
- SingleFileHTMLBuilder.init(self)
- finalize_media(self, local=True)
-
def setup(app):
app.add_builder(ReadtheDocsBuilder)
app.add_builder(ReadtheDocsDirectoryHTMLBuilder)
app.add_builder(ReadtheDocsSingleFileHTMLBuilder)
app.add_builder(ReadtheDocsSingleFileHTMLBuilderLocalMedia)
+ app.connect('builder-inited', finalize_media)
+ app.connect('builder-inited', finalize_comment_media)
+ app.connect('html-page-context', update_body)
app.connect('build-finished', copy_media)
# Comments
# app.connect('env-updated', add_comments_to_doctree)
app.add_directive(
- 'comment-configure', directive.CommentConfigurationDirective)
+ 'comment-configure', CommentConfigurationDirective)
app.add_builder(ReadtheDocsBuilderComments)
app.add_builder(ReadtheDocsDirectoryHTMLBuilderComments)
app.add_config_value(
diff --git a/requirements.txt b/requirements.txt
index 5eb2dce..b24ab50 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
sphinx==1.3.4
+pytest==2.8.5
diff --git a/setup.py b/setup.py
index 5452691..0e6c0d8 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@
# trying to add files...
include_package_data=True,
package_data={
- '': ['_static/*.js', '_static/*.js_t', '_static/*.css'],
+ '': ['_static/*.js', '_static/*.js_t', '_static/*.css', '_templates/*.tmpl'],
},
**extra_setup
)
diff --git a/tests/pyexample/conf.py b/tests/pyexample/conf.py
new file mode 100644
index 0000000..9ad6817
--- /dev/null
+++ b/tests/pyexample/conf.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+extensions = ['readthedocs_ext.readthedocs']
+
+templates_path = ['_templates']
+source_suffix = '.rst'
+master_doc = 'index'
+project = u'pyexample'
+copyright = u'2015, rtfd'
+author = u'rtfd'
+version = '0.1'
+release = '0.1'
+language = None
+exclude_patterns = ['_build']
+pygments_style = 'sphinx'
+todo_include_todos = False
+html_theme = 'alabaster'
+html_static_path = ['_static']
+htmlhelp_basename = 'pyexampledoc'
diff --git a/tests/pyexample/index.rst b/tests/pyexample/index.rst
new file mode 100644
index 0000000..1ea26b8
--- /dev/null
+++ b/tests/pyexample/index.rst
@@ -0,0 +1,9 @@
+.. pyexample documentation master file, created by
+ sphinx-quickstart on Fri May 29 13:34:37 2015.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to pyexample's documentation!
+=====================================
+
+Hey there friend!
diff --git a/tests/test_integration.py b/tests/test_integration.py
new file mode 100644
index 0000000..8213e49
--- /dev/null
+++ b/tests/test_integration.py
@@ -0,0 +1,55 @@
+import os
+import shutil
+import unittest
+import io
+
+from sphinx.application import Sphinx
+
+
+class LanguageIntegrationTests(unittest.TestCase):
+
+ def _run_test(self, test_dir, test_file, test_string, builder='html'):
+ os.chdir('tests/{0}'.format(test_dir))
+ try:
+ app = Sphinx(
+ srcdir='.',
+ confdir='.',
+ outdir='_build/%s' % builder,
+ doctreedir='_build/.doctrees',
+ buildername='%s' % builder,
+ )
+ app.build(force_all=True)
+ with io.open(test_file, encoding="utf-8") as fin:
+ text = fin.read().strip()
+ self.assertIn(test_string, text)
+ finally:
+ shutil.rmtree('_build')
+ os.chdir('../..')
+
+
+class IntegrationTests(LanguageIntegrationTests):
+
+ def test_integration(self):
+ self._run_test(
+ 'pyexample',
+ '_build/readthedocs/index.html',
+ 'Hey there friend!',
+ builder='readthedocs',
+ )
+
+ def test_media_integration(self):
+ self._run_test(
+ 'pyexample',
+ '_build/readthedocs/index.html',
+ 'media.readthedocs.org',
+ builder='readthedocs',
+ )
+
+ def test_included_js(self):
+ self._run_test(
+ 'pyexample',
+ '_build/readthedocs/index.html',
+ 'readthedocs-dynamic-include.js',
+ builder='readthedocs',
+ )
+
diff --git a/tox.ini b/tox.ini
index 8ea61db..e47de19 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,12 @@
[tox]
-envlist = lint
+envlist = py27,py34,lint
+
+[testenv]
+setenv =
+ LANG=C
+deps = -r{toxinidir}/requirements.txt
+commands =
+ py.test {posargs}
[testenv:lint]
deps =