summaryrefslogtreecommitdiff
path: root/sphinx/builders
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/builders')
-rw-r--r--sphinx/builders/__init__.py27
-rw-r--r--sphinx/builders/applehelp.py57
-rw-r--r--sphinx/builders/changes.py47
-rw-r--r--sphinx/builders/devhelp.py32
-rw-r--r--sphinx/builders/dummy.py4
-rw-r--r--sphinx/builders/epub.py69
-rw-r--r--sphinx/builders/epub3.py87
-rw-r--r--sphinx/builders/gettext.py26
-rw-r--r--sphinx/builders/html.py180
-rw-r--r--sphinx/builders/htmlhelp.py36
-rw-r--r--sphinx/builders/latex.py140
-rw-r--r--sphinx/builders/linkcheck.py139
-rw-r--r--sphinx/builders/manpage.py11
-rw-r--r--sphinx/builders/qthelp.py29
-rw-r--r--sphinx/builders/texinfo.py24
-rw-r--r--sphinx/builders/text.py12
-rw-r--r--sphinx/builders/websupport.py4
-rw-r--r--sphinx/builders/xml.py12
18 files changed, 601 insertions, 335 deletions
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index d0b6c2ca0..fe0c9c665 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -451,30 +451,3 @@ class Builder(object):
except AttributeError:
optname = '%s_%s' % (default, option)
return getattr(self.config, optname)
-
-
-BUILTIN_BUILDERS = {
- 'dummy': ('dummy', 'DummyBuilder'),
- 'html': ('html', 'StandaloneHTMLBuilder'),
- 'dirhtml': ('html', 'DirectoryHTMLBuilder'),
- 'singlehtml': ('html', 'SingleFileHTMLBuilder'),
- 'pickle': ('html', 'PickleHTMLBuilder'),
- 'json': ('html', 'JSONHTMLBuilder'),
- 'web': ('html', 'PickleHTMLBuilder'),
- 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'),
- 'devhelp': ('devhelp', 'DevhelpBuilder'),
- 'qthelp': ('qthelp', 'QtHelpBuilder'),
- 'applehelp': ('applehelp', 'AppleHelpBuilder'),
- 'epub': ('epub', 'EpubBuilder'),
- 'epub3': ('epub3', 'Epub3Builder'),
- 'latex': ('latex', 'LaTeXBuilder'),
- 'text': ('text', 'TextBuilder'),
- 'man': ('manpage', 'ManualPageBuilder'),
- 'texinfo': ('texinfo', 'TexinfoBuilder'),
- 'changes': ('changes', 'ChangesBuilder'),
- 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'),
- 'websupport': ('websupport', 'WebSupportBuilder'),
- 'gettext': ('gettext', 'MessageCatalogBuilder'),
- 'xml': ('xml', 'XMLBuilder'),
- 'pseudoxml': ('xml', 'PseudoXMLBuilder'),
-}
diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py
index a6c89b628..7db086953 100644
--- a/sphinx/builders/applehelp.py
+++ b/sphinx/builders/applehelp.py
@@ -13,14 +13,16 @@ from __future__ import print_function
import codecs
import pipes
-from os import path
+from os import path, environ
+import shlex
from sphinx.builders.html import StandaloneHTMLBuilder
-from sphinx.util import copy_static_entry
-from sphinx.util.osutil import copyfile, ensuredir
+from sphinx.config import string_classes
+from sphinx.util.osutil import copyfile, ensuredir, make_filename
from sphinx.util.console import bold
+from sphinx.util.fileutil import copy_asset
from sphinx.util.pycompat import htmlescape
-from sphinx.util.matching import compile_matchers
+from sphinx.util.matching import Matcher
from sphinx.errors import SphinxError
import plistlib
@@ -105,17 +107,15 @@ class AppleHelpBuilder(StandaloneHTMLBuilder):
self.finish_tasks.add_task(self.build_helpbook)
def copy_localized_files(self):
- source_dir = path.join(self.confdir,
- self.config.applehelp_locale + '.lproj')
+ source_dir = path.join(self.confdir, self.config.applehelp_locale + '.lproj')
target_dir = self.outdir
if path.isdir(source_dir):
self.info(bold('copying localized files... '), nonl=True)
- ctx = self.globalcontext.copy()
- matchers = compile_matchers(self.config.exclude_patterns)
- copy_static_entry(source_dir, target_dir, self, ctx,
- exclude_matchers=matchers)
+ excluded = Matcher(self.config.exclude_patterns + ['**/.*'])
+ copy_asset(source_dir, target_dir, excluded,
+ context=self.globalcontext, renderer=self.templates)
self.info('done')
@@ -179,14 +179,11 @@ class AppleHelpBuilder(StandaloneHTMLBuilder):
# Build the access page
self.info(bold('building access page...'), nonl=True)
- f = codecs.open(path.join(language_dir, '_access.html'), 'w')
- try:
+ with codecs.open(path.join(language_dir, '_access.html'), 'w') as f:
f.write(access_page_template % {
'toc': htmlescape(toc, quote=True),
'title': htmlescape(self.config.applehelp_title)
})
- finally:
- f.close()
self.info('done')
# Generate the help index
@@ -264,3 +261,35 @@ class AppleHelpBuilder(StandaloneHTMLBuilder):
self.info('done')
except OSError:
raise AppleHelpCodeSigningFailed('Command not found: %s' % args[0])
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.html')
+ app.add_builder(AppleHelpBuilder)
+
+ app.add_config_value('applehelp_bundle_name',
+ lambda self: make_filename(self.project), 'applehelp')
+ app.add_config_value('applehelp_bundle_id', None, 'applehelp', string_classes)
+ app.add_config_value('applehelp_dev_region', 'en-us', 'applehelp')
+ app.add_config_value('applehelp_bundle_version', '1', 'applehelp')
+ app.add_config_value('applehelp_icon', None, 'applehelp', string_classes)
+ app.add_config_value('applehelp_kb_product',
+ lambda self: '%s-%s' % (make_filename(self.project), self.release),
+ 'applehelp')
+ app.add_config_value('applehelp_kb_url', None, 'applehelp', string_classes)
+ app.add_config_value('applehelp_remote_url', None, 'applehelp', string_classes)
+ app.add_config_value('applehelp_index_anchors', False, 'applehelp', string_classes)
+ app.add_config_value('applehelp_min_term_length', None, 'applehelp', string_classes)
+ app.add_config_value('applehelp_stopwords',
+ lambda self: self.language or 'en', 'applehelp')
+ app.add_config_value('applehelp_locale', lambda self: self.language or 'en', 'applehelp')
+ app.add_config_value('applehelp_title', lambda self: self.project + ' Help', 'applehelp')
+ app.add_config_value('applehelp_codesign_identity',
+ lambda self: environ.get('CODE_SIGN_IDENTITY', None),
+ 'applehelp'),
+ app.add_config_value('applehelp_codesign_flags',
+ lambda self: shlex.split(environ.get('OTHER_CODE_SIGN_FLAGS', '')),
+ 'applehelp'),
+ app.add_config_value('applehelp_indexer_path', '/usr/bin/hiutil', 'applehelp')
+ app.add_config_value('applehelp_codesign_path', '/usr/bin/codesign', 'applehelp')
+ app.add_config_value('applehelp_disable_external_tools', False, None)
diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py
index c077b7dd2..1bccb67d9 100644
--- a/sphinx/builders/changes.py
+++ b/sphinx/builders/changes.py
@@ -15,12 +15,12 @@ from os import path
from six import iteritems
from sphinx import package_dir
-from sphinx.util import copy_static_entry
from sphinx.locale import _
from sphinx.theming import Theme
from sphinx.builders import Builder
from sphinx.util.osutil import ensuredir, os_path
from sphinx.util.console import bold
+from sphinx.util.fileutil import copy_asset_file
from sphinx.util.pycompat import htmlescape
@@ -101,16 +101,10 @@ class ChangesBuilder(Builder):
'show_copyright': self.config.html_show_copyright,
'show_sphinx': self.config.html_show_sphinx,
}
- f = codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8')
- try:
+ with codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8') as f:
f.write(self.templates.render('changes/frameset.html', ctx))
- finally:
- f.close()
- f = codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8')
- try:
+ with codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8') as f:
f.write(self.templates.render('changes/versionchanges.html', ctx))
- finally:
- f.close()
hltext = ['.. versionadded:: %s' % version,
'.. versionchanged:: %s' % version,
@@ -126,35 +120,28 @@ class ChangesBuilder(Builder):
self.info(bold('copying source files...'))
for docname in self.env.all_docs:
- f = codecs.open(self.env.doc2path(docname), 'r',
- self.env.config.source_encoding)
- try:
- lines = f.readlines()
- except UnicodeDecodeError:
- self.warn('could not read %r for changelog creation' % docname)
- continue
- finally:
- f.close()
+ with codecs.open(self.env.doc2path(docname), 'r',
+ self.env.config.source_encoding) as f:
+ try:
+ lines = f.readlines()
+ except UnicodeDecodeError:
+ self.warn('could not read %r for changelog creation' % docname)
+ continue
targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html'
ensuredir(path.dirname(targetfn))
- f = codecs.open(targetfn, 'w', 'utf-8')
- try:
+ with codecs.open(targetfn, 'w', 'utf-8') as f:
text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
ctx = {
'filename': self.env.doc2path(docname, None),
'text': text
}
f.write(self.templates.render('changes/rstsource.html', ctx))
- finally:
- f.close()
themectx = dict(('theme_' + key, val) for (key, val) in
iteritems(self.theme.get_options({})))
- copy_static_entry(path.join(package_dir, 'themes', 'default',
- 'static', 'default.css_t'),
- self.outdir, self, themectx)
- copy_static_entry(path.join(package_dir, 'themes', 'basic',
- 'static', 'basic.css'),
- self.outdir, self)
+ copy_asset_file(path.join(package_dir, 'themes', 'default', 'static', 'default.css_t'),
+ self.outdir, context=themectx, renderer=self.templates)
+ copy_asset_file(path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'),
+ self.outdir)
def hl(self, text, version):
text = htmlescape(text)
@@ -165,3 +152,7 @@ class ChangesBuilder(Builder):
def finish(self):
pass
+
+
+def setup(app):
+ app.add_builder(ChangesBuilder)
diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py
index 523941110..fd6f3400e 100644
--- a/sphinx/builders/devhelp.py
+++ b/sphinx/builders/devhelp.py
@@ -13,32 +13,19 @@
from __future__ import absolute_import
import re
+import gzip
from os import path
from docutils import nodes
from sphinx import addnodes
+from sphinx.util.osutil import make_filename
from sphinx.builders.html import StandaloneHTMLBuilder
try:
import xml.etree.ElementTree as etree
except ImportError:
- try:
- import lxml.etree as etree
- except ImportError:
- try:
- import elementtree.ElementTree as etree
- except ImportError:
- import cElementTree as etree
-
-try:
- import gzip
-
- def comp_open(filename, mode='rb'):
- return gzip.open(filename + '.gz', mode)
-except ImportError:
- def comp_open(filename, mode='rb'):
- return open(filename, mode)
+ import lxml.etree as etree
class DevhelpBuilder(StandaloneHTMLBuilder):
@@ -128,8 +115,13 @@ class DevhelpBuilder(StandaloneHTMLBuilder):
write_index(title, refs, subitems)
# Dump the XML file
- f = comp_open(path.join(outdir, outname + '.devhelp'), 'w')
- try:
+ xmlfile = path.join(outdir, outname + '.devhelp.gz')
+ with gzip.open(xmlfile, 'w') as f:
tree.write(f, 'utf-8')
- finally:
- f.close()
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.html')
+ app.add_builder(DevhelpBuilder)
+
+ app.add_config_value('devhelp_basename', lambda self: make_filename(self.project), None)
diff --git a/sphinx/builders/dummy.py b/sphinx/builders/dummy.py
index 75b834c2b..b119d9687 100644
--- a/sphinx/builders/dummy.py
+++ b/sphinx/builders/dummy.py
@@ -34,3 +34,7 @@ class DummyBuilder(Builder):
def finish(self):
pass
+
+
+def setup(app):
+ app.add_builder(DummyBuilder)
diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py
index ade887e56..b4b657468 100644
--- a/sphinx/builders/epub.py
+++ b/sphinx/builders/epub.py
@@ -29,7 +29,7 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
-from sphinx.util.osutil import ensuredir, copyfile, EEXIST
+from sphinx.util.osutil import ensuredir, copyfile, make_filename, EEXIST
from sphinx.util.smartypants import sphinx_smarty_pants as ssp
from sphinx.util.console import brown
@@ -179,7 +179,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
META-INF/container.xml. Afterwards, all necessary files are zipped to an
epub file.
"""
- name = 'epub'
+ name = 'epub2'
# don't copy the reST source
copysource = False
@@ -188,8 +188,12 @@ class EpubBuilder(StandaloneHTMLBuilder):
# don't add links
add_permalinks = False
+ # don't use # as current path. ePub check reject it.
+ allow_sharp_as_current_path = False
# don't add sidebar etc.
embedded = True
+ # disable download role
+ download_support = False
# dont' create links to original images from images
html_scaled_image_link = False
# don't generate search index or include search page
@@ -223,6 +227,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
self.link_suffix = '.xhtml'
self.playorder = 0
self.tocid = 0
+ self.use_index = self.get_builder_config('use_index', 'epub')
def get_theme_config(self):
return self.config.epub_theme, self.config.epub_theme_options
@@ -485,6 +490,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
else:
super(EpubBuilder, self).copy_image_files()
+ def copy_download_files(self):
+ pass
+
def handle_page(self, pagename, addctx, templatename='page.html',
outfilename=None, event_arg=None):
"""Create a rendered page.
@@ -493,6 +501,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
attributes.
"""
if pagename.startswith('genindex'):
+ if not self.use_index:
+ return
self.fix_genindex(addctx['genindexentries'])
addctx['doctype'] = self.doctype
StandaloneHTMLBuilder.handle_page(self, pagename, addctx, templatename,
@@ -511,11 +521,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
def build_mimetype(self, outdir, outname):
"""Write the metainfo file mimetype."""
self.info('writing %s file...' % outname)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.mimetype_template)
- finally:
- f.close()
def build_container(self, outdir, outname):
"""Write the metainfo file META-INF/cointainer.xml."""
@@ -526,11 +533,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
except OSError as err:
if err.errno != EEXIST:
raise
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.container_template)
- finally:
- f.close()
def content_metadata(self, files, spine, guide):
"""Create a dictionary with all metadata for the content.opf
@@ -565,8 +569,11 @@ class EpubBuilder(StandaloneHTMLBuilder):
self.files = []
self.ignored_files = ['.buildinfo', 'mimetype', 'content.opf',
'toc.ncx', 'META-INF/container.xml',
+ 'Thumbs.db', 'ehthumbs.db', '.DS_Store',
self.config.epub_basename + '.epub'] + \
self.config.epub_exclude_files
+ if not self.use_index:
+ self.ignored_files.append('genindex' + self.out_suffix)
for root, dirs, files in os.walk(outdir):
for fn in files:
filename = path.join(root, fn)[olen:]
@@ -604,7 +611,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
'idref': self.esc(self.make_id(info[0] + self.out_suffix))
})
spinefiles.add(info[0] + self.out_suffix)
- if self.get_builder_config('use_index', 'epub'):
+ if self.use_index:
spine.append(self.spine_template % {
'idref': self.esc(self.make_id('genindex' + self.out_suffix))
})
@@ -677,12 +684,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
guide = '\n'.join(guide)
# write the project file
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(content_tmpl %
self.content_metadata(projectfiles, spine, guide))
- finally:
- f.close()
def new_navpoint(self, node, level, incr=True):
"""Create a new entry in the toc from the node at given level."""
@@ -774,11 +778,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
navpoints = self.build_navpoints(refnodes)
level = max(item['level'] for item in self.refnodes)
level = min(level, self.config.epub_tocdepth)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.toc_template % self.toc_metadata(level, navpoints))
- finally:
- f.close()
def build_epub(self, outdir, outname):
"""Write the epub file.
@@ -797,3 +798,33 @@ class EpubBuilder(StandaloneHTMLBuilder):
fp = path.join(outdir, file)
epub.write(fp, file, zipfile.ZIP_DEFLATED)
epub.close()
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.html')
+ app.add_builder(EpubBuilder)
+
+ # config values
+ app.add_config_value('epub_basename', lambda self: make_filename(self.project), None)
+ app.add_config_value('epub_theme', 'epub', 'html')
+ app.add_config_value('epub_theme_options', {}, 'html')
+ app.add_config_value('epub_title', lambda self: self.html_title, 'html')
+ app.add_config_value('epub_author', 'unknown', 'html')
+ app.add_config_value('epub_language', lambda self: self.language or 'en', 'html')
+ app.add_config_value('epub_publisher', 'unknown', 'html')
+ app.add_config_value('epub_copyright', lambda self: self.copyright, 'html')
+ app.add_config_value('epub_identifier', 'unknown', 'html')
+ app.add_config_value('epub_scheme', 'unknown', 'html')
+ app.add_config_value('epub_uid', 'unknown', 'env')
+ app.add_config_value('epub_cover', (), 'env')
+ app.add_config_value('epub_guide', (), 'env')
+ app.add_config_value('epub_pre_files', [], 'env')
+ app.add_config_value('epub_post_files', [], 'env')
+ app.add_config_value('epub_exclude_files', [], 'env')
+ app.add_config_value('epub_tocdepth', 3, 'env')
+ app.add_config_value('epub_tocdup', True, 'env')
+ app.add_config_value('epub_tocscope', 'default', 'env')
+ app.add_config_value('epub_fix_images', False, 'env')
+ app.add_config_value('epub_max_image_width', 0, 'env')
+ app.add_config_value('epub_show_urls', 'inline', 'html')
+ app.add_config_value('epub_use_index', lambda self: self.html_use_index, 'html')
diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py
index d792bd6e9..ae799986e 100644
--- a/sphinx/builders/epub3.py
+++ b/sphinx/builders/epub3.py
@@ -14,6 +14,7 @@ import codecs
from os import path
from datetime import datetime
+from sphinx.config import string_classes
from sphinx.builders.epub import EpubBuilder
@@ -52,7 +53,8 @@ NAVLIST_INDENT = ' '
PACKAGE_DOC_TEMPLATE = u'''\
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" xml:lang="%(lang)s"
- unique-identifier="%(uid)s">
+ unique-identifier="%(uid)s"
+ prefix="ibooks: http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/">
<metadata xmlns:opf="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:language>%(lang)s</dc:language>
@@ -65,6 +67,10 @@ PACKAGE_DOC_TEMPLATE = u'''\
<dc:identifier id="%(uid)s">%(id)s</dc:identifier>
<dc:date>%(date)s</dc:date>
<meta property="dcterms:modified">%(date)s</meta>
+ <meta property="ibooks:version">%(version)s</meta>
+ <meta property="ibooks:specified-fonts">true</meta>
+ <meta property="ibooks:binding">true</meta>
+ <meta property="ibooks:scroll-axis">%(ibook_scroll_axis)s</meta>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
@@ -94,7 +100,7 @@ class Epub3Builder(EpubBuilder):
and META-INF/container.xml. Afterwards, all necessary files are zipped to
an epub file.
"""
- name = 'epub3'
+ name = 'epub'
navigation_doc_template = NAVIGATION_DOC_TEMPLATE
navlist_template = NAVLIST_TEMPLATE
@@ -122,13 +128,43 @@ class Epub3Builder(EpubBuilder):
"""
metadata = super(Epub3Builder, self).content_metadata(
files, spine, guide)
- metadata['description'] = self.esc(self.config.epub3_description)
- metadata['contributor'] = self.esc(self.config.epub3_contributor)
- metadata['page_progression_direction'] = self.esc(
- self.config.epub3_page_progression_direction) or 'default'
+ metadata['description'] = self.esc(self.config.epub_description)
+ metadata['contributor'] = self.esc(self.config.epub_contributor)
+ metadata['page_progression_direction'] = self._page_progression_direction()
+ metadata['ibook_scroll_axis'] = self._ibook_scroll_axis()
metadata['date'] = self.esc(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
+ metadata['version'] = self.esc(self.config.version)
return metadata
+ def _page_progression_direction(self):
+ if self.config.epub_writing_mode == 'horizontal':
+ page_progression_direction = 'ltr'
+ elif self.config.epub_writing_mode == 'vertical':
+ page_progression_direction = 'rtl'
+ else:
+ page_progression_direction = 'default'
+ return page_progression_direction
+
+ def _ibook_scroll_axis(self):
+ if self.config.epub_writing_mode == 'horizontal':
+ scroll_axis = 'vertical'
+ elif self.config.epub_writing_mode == 'vertical':
+ scroll_axis = 'horizontal'
+ else:
+ scroll_axis = 'default'
+ return scroll_axis
+
+ def _css_writing_mode(self):
+ if self.config.epub_writing_mode == 'vertical':
+ editing_mode = 'vertical-rl'
+ else:
+ editing_mode = 'horizontal-tb'
+ return editing_mode
+
+ def prepare_writing(self, docnames):
+ super(Epub3Builder, self).prepare_writing(docnames)
+ self.globalcontext['theme_writing_mode'] = self._css_writing_mode()
+
def new_navlist(self, node, level, has_child):
"""Create a new entry in the toc from the node at given level."""
# XXX Modifies the node
@@ -211,12 +247,37 @@ class Epub3Builder(EpubBuilder):
# 'includehidden'
refnodes = self.refnodes
navlist = self.build_navlist(refnodes)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.navigation_doc_template %
self.navigation_doc_metadata(navlist))
- finally:
- f.close()
- # Add nav.xhtml to epub file
- if outname not in self.files:
- self.files.append(outname)
+
+ # Add nav.xhtml to epub file
+ if outname not in self.files:
+ self.files.append(outname)
+
+
+def validate_config_values(app):
+ if app.config.epub3_description is not None:
+ app.warn('epub3_description is deprecated. Use epub_description instead.')
+ app.config.epub_description = app.config.epub3_description
+
+ if app.config.epub3_contributor is not None:
+ app.warn('epub3_contributor is deprecated. Use epub_contributor instead.')
+ app.config.epub_contributor = app.config.epub3_contributor
+
+ if app.config.epub3_page_progression_direction is not None:
+ app.warn('epub3_page_progression_direction option is deprecated'
+ ' from 1.5. Use epub_writing_mode instead.')
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.epub')
+ app.add_builder(Epub3Builder)
+ app.connect('builder-inited', validate_config_values)
+
+ app.add_config_value('epub_description', '', 'epub3', string_classes)
+ app.add_config_value('epub_contributor', 'unknown', 'epub3', string_classes)
+ app.add_config_value('epub_writing_mode', 'horizontal', 'epub3', string_classes)
+ app.add_config_value('epub3_description', None, 'epub3', string_classes)
+ app.add_config_value('epub3_contributor', None, 'epub3', string_classes)
+ app.add_config_value('epub3_page_progression_direction', None, 'epub3', string_classes)
diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py
index 9edbdf1d3..e118cde99 100644
--- a/sphinx/builders/gettext.py
+++ b/sphinx/builders/gettext.py
@@ -22,6 +22,7 @@ from six import iteritems
from sphinx.builders import Builder
from sphinx.util import split_index_msg
+from sphinx.util.tags import Tags
from sphinx.util.nodes import extract_messages, traverse_translatable_index
from sphinx.util.osutil import safe_relpath, ensuredir, canon_path
from sphinx.util.i18n import find_catalog
@@ -79,6 +80,16 @@ class MsgOrigin(object):
self.uid = uuid4().hex
+class I18nTags(Tags):
+ """Dummy tags module for I18nBuilder.
+
+ To translate all text inside of only nodes, this class
+ always returns True value even if no tags are defined.
+ """
+ def eval_condition(self, condition):
+ return True
+
+
class I18nBuilder(Builder):
"""
General i18n builder.
@@ -93,6 +104,7 @@ class I18nBuilder(Builder):
def init(self):
Builder.init(self)
+ self.tags = I18nTags()
self.catalogs = defaultdict(Catalog)
def get_target_uri(self, docname, typ=None):
@@ -212,8 +224,7 @@ class MessageCatalogBuilder(I18nBuilder):
ensuredir(path.join(self.outdir, path.dirname(textdomain)))
pofn = path.join(self.outdir, textdomain + '.pot')
- pofile = open(pofn, 'w', encoding='utf-8')
- try:
+ with open(pofn, 'w', encoding='utf-8') as pofile:
pofile.write(POHEADER % data)
for message in catalog.messages:
@@ -236,5 +247,12 @@ class MessageCatalogBuilder(I18nBuilder):
replace('\n', '\\n"\n"')
pofile.write('msgid "%s"\nmsgstr ""\n\n' % message)
- finally:
- pofile.close()
+
+def setup(app):
+ app.add_builder(MessageCatalogBuilder)
+
+ app.add_config_value('gettext_compact', True, 'gettext')
+ app.add_config_value('gettext_location', True, 'gettext')
+ app.add_config_value('gettext_uuid', False, 'gettext')
+ app.add_config_value('gettext_auto_build', True, 'env')
+ app.add_config_value('gettext_additional_targets', [], 'env')
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index f5d8cf549..e13d752d7 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -27,13 +27,15 @@ from docutils.frontend import OptionParser
from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import package_dir, __display_version__
-from sphinx.util import jsonimpl, copy_static_entry, copy_extra_entry
+from sphinx.util import jsonimpl
from sphinx.util.i18n import format_date
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
movefile, copyfile
from sphinx.util.nodes import inline_all_toctrees
-from sphinx.util.matching import patmatch, compile_matchers
-from sphinx.locale import _
+from sphinx.util.fileutil import copy_asset
+from sphinx.util.matching import patmatch, Matcher, DOTFILES
+from sphinx.config import string_classes
+from sphinx.locale import _, l_
from sphinx.search import js_index
from sphinx.theming import Theme
from sphinx.builders import Builder
@@ -80,8 +82,10 @@ class StandaloneHTMLBuilder(Builder):
'image/gif', 'image/jpeg']
searchindex_filename = 'searchindex.js'
add_permalinks = True
+ allow_sharp_as_current_path = True
embedded = False # for things like HTML help or Qt help: suppresses sidebar
search = True # for things like HTML help and Apple help: suppress search
+ download_support = True # enable download role
# This is a class attribute because it is mutated by Sphinx.add_javascript.
script_files = ['_static/jquery.js', '_static/underscore.js',
@@ -120,6 +124,7 @@ class StandaloneHTMLBuilder(Builder):
if self.config.language is not None:
if self._get_translations_js():
self.script_files.append('_static/translations.js')
+ self.use_index = self.get_builder_config('use_index', 'html')
def _get_translations_js(self):
candidates = [path.join(package_dir, 'locale', self.config.language,
@@ -158,16 +163,11 @@ class StandaloneHTMLBuilder(Builder):
self.config.trim_doctest_flags)
def init_translator_class(self):
- if self.translator_class is not None:
- pass
- elif self.config.html_translator_class:
- self.translator_class = self.app.import_object(
- self.config.html_translator_class,
- 'html_translator_class setting')
- elif self.config.html_use_smartypants:
- self.translator_class = SmartyPantsHTMLTranslator
- else:
- self.translator_class = HTMLTranslator
+ if self.translator_class is None:
+ if self.config.html_use_smartypants:
+ self.translator_class = SmartyPantsHTMLTranslator
+ else:
+ self.translator_class = HTMLTranslator
def get_outdated_docs(self):
cfgdict = dict((name, self.config[name])
@@ -177,8 +177,7 @@ class StandaloneHTMLBuilder(Builder):
self.tags_hash = get_stable_hash(sorted(self.tags))
old_config_hash = old_tags_hash = ''
try:
- fp = open(path.join(self.outdir, '.buildinfo'))
- try:
+ with open(path.join(self.outdir, '.buildinfo')) as fp:
version = fp.readline()
if version.rstrip() != '# Sphinx build info version 1':
raise ValueError
@@ -189,8 +188,6 @@ class StandaloneHTMLBuilder(Builder):
tag, old_tags_hash = fp.readline().strip().split(': ')
if tag != 'tags':
raise ValueError
- finally:
- fp.close()
except ValueError:
self.warn('unsupported build info format in %r, building all' %
path.join(self.outdir, '.buildinfo'))
@@ -314,7 +311,7 @@ class StandaloneHTMLBuilder(Builder):
self.relations = self.env.collect_relations()
rellinks = []
- if self.get_builder_config('use_index', 'html'):
+ if self.use_index:
rellinks.append(('genindex', _('General Index'), 'I', _('index')))
for indexname, indexcls, content, collapse in self.domain_indices:
# if it has a short name
@@ -344,6 +341,7 @@ class StandaloneHTMLBuilder(Builder):
show_sphinx = self.config.html_show_sphinx,
has_source = self.config.html_copy_source,
show_source = self.config.html_show_sourcelink,
+ sourcelink_suffix = self.config.html_sourcelink_suffix,
file_suffix = self.out_suffix,
script_files = self.script_files,
language = self.config.language,
@@ -407,15 +405,21 @@ class StandaloneHTMLBuilder(Builder):
# title rendered as HTML
title = self.env.longtitles.get(docname)
title = title and self.render_partial(title)['title'] or ''
+
+ # Suffix for the document
+ source_suffix = path.splitext(self.env.doc2path(docname))[1]
+
# the name for the copied source
- sourcename = self.config.html_copy_source and docname + '.txt' or ''
+ if self.config.html_copy_source:
+ sourcename = docname + source_suffix
+ if source_suffix != self.config.html_sourcelink_suffix:
+ sourcename += self.config.html_sourcelink_suffix
+ else:
+ sourcename = ''
# metadata for the document
meta = self.env.metadata.get(docname)
- # Suffix for the document
- source_suffix = '.' + self.env.doc2path(docname).split('.')[-1]
-
# local TOC and global TOC tree
self_toc = self.env.get_toc_for(docname, self)
toc = self.render_partial(self_toc)['fragment']
@@ -476,7 +480,7 @@ class StandaloneHTMLBuilder(Builder):
self.info(bold('generating indices...'), nonl=1)
# the global general index
- if self.get_builder_config('use_index', 'html'):
+ if self.use_index:
self.write_genindex()
# the global domain-specific indices
@@ -586,9 +590,8 @@ class StandaloneHTMLBuilder(Builder):
self.info(bold('copying static files... '), nonl=True)
ensuredir(path.join(self.outdir, '_static'))
# first, create pygments style file
- f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w')
- f.write(self.highlighter.get_stylesheet())
- f.close()
+ with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
+ f.write(self.highlighter.get_stylesheet())
# then, copy translations JavaScript file
if self.config.language is not None:
jsfile = self._get_translations_js()
@@ -610,21 +613,19 @@ class StandaloneHTMLBuilder(Builder):
# then, copy over theme-supplied static files
if self.theme:
- themeentries = [path.join(themepath, 'static')
- for themepath in self.theme.get_dirchain()[::-1]]
- for entry in themeentries:
- copy_static_entry(entry, path.join(self.outdir, '_static'),
- self, ctx)
+ for theme_path in self.theme.get_dirchain()[::-1]:
+ entry = path.join(theme_path, 'static')
+ copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES,
+ context=ctx, renderer=self.templates)
# then, copy over all user-supplied static files
- staticentries = [path.join(self.confdir, spath)
- for spath in self.config.html_static_path]
- matchers = compile_matchers(self.config.exclude_patterns)
- for entry in staticentries:
+ excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
+ for static_path in self.config.html_static_path:
+ entry = path.join(self.confdir, static_path)
if not path.exists(entry):
self.warn('html_static_path entry %r does not exist' % entry)
continue
- copy_static_entry(entry, path.join(self.outdir, '_static'), self,
- ctx, exclude_matchers=matchers)
+ copy_asset(entry, path.join(self.outdir, '_static'), excluded,
+ context=ctx, renderer=self.templates)
# copy logo and favicon files if not already in static path
if self.config.html_logo:
logobase = path.basename(self.config.html_logo)
@@ -647,27 +648,25 @@ class StandaloneHTMLBuilder(Builder):
def copy_extra_files(self):
# copy html_extra_path files
self.info(bold('copying extra files... '), nonl=True)
- extraentries = [path.join(self.confdir, epath)
- for epath in self.config.html_extra_path]
- matchers = compile_matchers(self.config.exclude_patterns)
- for entry in extraentries:
+ excluded = Matcher(self.config.exclude_patterns)
+
+ for extra_path in self.config.html_extra_path:
+ entry = path.join(self.confdir, extra_path)
if not path.exists(entry):
self.warn('html_extra_path entry %r does not exist' % entry)
continue
- copy_extra_entry(entry, self.outdir, matchers)
+
+ copy_asset(entry, self.outdir, excluded)
self.info('done')
def write_buildinfo(self):
# write build info file
- fp = open(path.join(self.outdir, '.buildinfo'), 'w')
- try:
+ with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
fp.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building'
' these files. When it is not found, a full rebuild will'
' be done.\nconfig: %s\ntags: %s\n' %
(self.config_hash, self.tags_hash))
- finally:
- fp.close()
def cleanup(self):
# clean up theme stuff
@@ -707,10 +706,8 @@ class StandaloneHTMLBuilder(Builder):
f = codecs.open(searchindexfn, 'r', encoding='utf-8')
else:
f = open(searchindexfn, 'rb')
- try:
+ with f:
self.indexer.load(f, self.indexer_format)
- finally:
- f.close()
except (IOError, OSError, ValueError):
if keep:
self.warn('search index couldn\'t be loaded, but not all '
@@ -722,7 +719,12 @@ class StandaloneHTMLBuilder(Builder):
def index_page(self, pagename, doctree, title):
# only index pages with title
if self.indexer is not None and title:
- self.indexer.feed(pagename, title, doctree)
+ filename = self.env.doc2path(pagename, base=None)
+ try:
+ self.indexer.feed(pagename, filename, title, doctree)
+ except TypeError:
+ # fallback for old search-adapters
+ self.indexer.feed(pagename, title, doctree)
def _get_local_toctree(self, docname, collapse=True, **kwds):
if 'includehidden' not in kwds:
@@ -785,6 +787,8 @@ class StandaloneHTMLBuilder(Builder):
elif not resource:
otheruri = self.get_target_uri(otheruri)
uri = relative_uri(baseuri, otheruri) or '#'
+ if uri == '#' and not self.allow_sharp_as_current_path:
+ uri = baseuri
return uri
ctx['pathto'] = pathto
@@ -824,11 +828,8 @@ class StandaloneHTMLBuilder(Builder):
# outfilename's path is in general different from self.outdir
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace')
- try:
+ with codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace') as f:
f.write(output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
if self.copysource and ctx.get('sourcename'):
@@ -845,8 +846,7 @@ class StandaloneHTMLBuilder(Builder):
def dump_inventory(self):
self.info(bold('dumping object inventory... '), nonl=True)
- f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
- try:
+ with open(path.join(self.outdir, INVENTORY_FILENAME), 'wb') as f:
f.write((u'# Sphinx inventory version 2\n'
u'# Project: %s\n'
u'# Version: %s\n'
@@ -868,8 +868,6 @@ class StandaloneHTMLBuilder(Builder):
(u'%s %s:%s %s %s %s\n' % (name, domainname, type,
prio, uri, dispname)).encode('utf-8')))
f.write(compressor.flush())
- finally:
- f.close()
self.info('done')
def dump_search_index(self):
@@ -884,10 +882,8 @@ class StandaloneHTMLBuilder(Builder):
f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8')
else:
f = open(searchindexfn + '.tmp', 'wb')
- try:
+ with f:
self.indexer.dump(f, self.indexer_format)
- finally:
- f.close()
movefile(searchindexfn + '.tmp', searchindexfn)
self.info('done')
@@ -1106,7 +1102,9 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
self.theme = None # no theme necessary
self.templates = None # no template bridge necessary
self.init_translator_class()
+ self.init_templates()
self.init_highlighter()
+ self.use_index = self.get_builder_config('use_index', 'html')
def get_target_uri(self, docname, typ=None):
if docname == 'index':
@@ -1120,10 +1118,8 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
f = codecs.open(filename, 'w', encoding='utf-8')
else:
f = open(filename, 'wb')
- try:
+ with f:
self.implementation.dump(context, f, *self.additional_dump_args)
- finally:
- f.close()
def handle_page(self, pagename, ctx, templatename='page.html',
outfilename=None, event_arg=None):
@@ -1201,3 +1197,59 @@ class JSONHTMLBuilder(SerializingHTMLBuilder):
def init(self):
SerializingHTMLBuilder.init(self)
+
+
+def validate_config_values(app):
+ if app.config.html_translator_class:
+ app.warn('html_translator_class is deprecated. '
+ 'Use Sphinx.set_translator() API instead.')
+
+
+def setup(app):
+ # builders
+ app.add_builder(StandaloneHTMLBuilder)
+ app.add_builder(DirectoryHTMLBuilder)
+ app.add_builder(SingleFileHTMLBuilder)
+ app.add_builder(PickleHTMLBuilder)
+ app.add_builder(JSONHTMLBuilder)
+
+ app.connect('builder-inited', validate_config_values)
+
+ # config values
+ app.add_config_value('html_theme', 'alabaster', 'html')
+ app.add_config_value('html_theme_path', [], 'html')
+ app.add_config_value('html_theme_options', {}, 'html')
+ app.add_config_value('html_title',
+ lambda self: l_('%s %s documentation') % (self.project, self.release),
+ 'html', string_classes)
+ app.add_config_value('html_short_title', lambda self: self.html_title, 'html')
+ app.add_config_value('html_style', None, 'html', string_classes)
+ app.add_config_value('html_logo', None, 'html', string_classes)
+ app.add_config_value('html_favicon', None, 'html', string_classes)
+ app.add_config_value('html_static_path', [], 'html')
+ app.add_config_value('html_extra_path', [], 'html')
+ app.add_config_value('html_last_updated_fmt', None, 'html', string_classes)
+ app.add_config_value('html_use_smartypants', True, 'html')
+ app.add_config_value('html_sidebars', {}, 'html')
+ app.add_config_value('html_additional_pages', {}, 'html')
+ app.add_config_value('html_use_modindex', True, 'html') # deprecated
+ app.add_config_value('html_domain_indices', True, 'html', [list])
+ app.add_config_value('html_add_permalinks', u'\u00B6', 'html')
+ app.add_config_value('html_use_index', True, 'html')
+ app.add_config_value('html_split_index', False, 'html')
+ app.add_config_value('html_copy_source', True, 'html')
+ app.add_config_value('html_show_sourcelink', True, 'html')
+ app.add_config_value('html_sourcelink_suffix', '.txt', 'html')
+ app.add_config_value('html_use_opensearch', '', 'html')
+ app.add_config_value('html_file_suffix', None, 'html', string_classes)
+ app.add_config_value('html_link_suffix', None, 'html', string_classes)
+ app.add_config_value('html_show_copyright', True, 'html')
+ app.add_config_value('html_show_sphinx', True, 'html')
+ app.add_config_value('html_context', {}, 'html')
+ app.add_config_value('html_output_encoding', 'utf-8', 'html')
+ app.add_config_value('html_compact_lists', True, 'html')
+ app.add_config_value('html_secnumber_suffix', '. ', 'html')
+ app.add_config_value('html_search_language', None, 'html', string_classes)
+ app.add_config_value('html_search_options', {}, 'html')
+ app.add_config_value('html_search_scorer', '', None)
+ app.add_config_value('html_scaled_image_link', True, 'html')
diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py
index f1626321e..ecc752b60 100644
--- a/sphinx/builders/htmlhelp.py
+++ b/sphinx/builders/htmlhelp.py
@@ -18,6 +18,7 @@ from os import path
from docutils import nodes
from sphinx import addnodes
+from sphinx.util.osutil import make_filename
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.util.pycompat import htmlescape
@@ -197,18 +198,22 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
def handle_finish(self):
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
+ def write_doc(self, docname, doctree):
+ for node in doctree.traverse(nodes.reference):
+ # add ``target=_blank`` attributes to external links
+ if node.get('internal') is None and 'refuri' in node:
+ node['target'] = '_blank'
+
+ StandaloneHTMLBuilder.write_doc(self, docname, doctree)
+
def build_hhx(self, outdir, outname):
self.info('dumping stopword list...')
- f = self.open_file(outdir, outname+'.stp')
- try:
+ with self.open_file(outdir, outname+'.stp') as f:
for word in sorted(stopwords):
print(word, file=f)
- finally:
- f.close()
self.info('writing project file...')
- f = self.open_file(outdir, outname+'.hhp')
- try:
+ with self.open_file(outdir, outname+'.hhp') as f:
f.write(project_template % {
'outname': outname,
'title': self.config.html_title,
@@ -227,12 +232,9 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
fn.endswith('.html'):
print(path.join(root, fn)[olen:].replace(os.sep, '\\'),
file=f)
- finally:
- f.close()
self.info('writing TOC file...')
- f = self.open_file(outdir, outname+'.hhc')
- try:
+ with self.open_file(outdir, outname+'.hhc') as f:
f.write(contents_header)
# special books
f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
@@ -270,13 +272,10 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
for node in tocdoc.traverse(istoctree):
write_toc(node)
f.write(contents_footer)
- finally:
- f.close()
self.info('writing index file...')
index = self.env.create_index(self)
- f = self.open_file(outdir, outname+'.hhk')
- try:
+ with self.open_file(outdir, outname+'.hhk') as f:
f.write('<UL>\n')
def write_index(title, refs, subitems):
@@ -306,5 +305,10 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
for title, (refs, subitems, key_) in group:
write_index(title, refs, subitems)
f.write('</UL>\n')
- finally:
- f.close()
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.html')
+ app.add_builder(HTMLHelpBuilder)
+
+ app.add_config_value('htmlhelp_basename', lambda self: make_filename(self.project), None)
diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py
index ac26e33c9..0ef0d70d4 100644
--- a/sphinx/builders/latex.py
+++ b/sphinx/builders/latex.py
@@ -11,7 +11,6 @@
import os
from os import path
-import warnings
from six import iteritems
from docutils import nodes
@@ -19,14 +18,16 @@ from docutils.io import FileOutput
from docutils.utils import new_document
from docutils.frontend import OptionParser
-from sphinx import package_dir, addnodes
+from sphinx import package_dir, addnodes, highlighting
from sphinx.util import texescape
+from sphinx.config import string_classes, ENUM
from sphinx.errors import SphinxError
from sphinx.locale import _
from sphinx.builders import Builder
from sphinx.environment import NoUri
from sphinx.util.nodes import inline_all_toctrees
-from sphinx.util.osutil import SEP, copyfile
+from sphinx.util.fileutil import copy_asset_file
+from sphinx.util.osutil import SEP, make_filename
from sphinx.util.console import bold, darkgreen
from sphinx.writers.latex import LaTeXWriter
@@ -38,27 +39,12 @@ class LaTeXBuilder(Builder):
name = 'latex'
format = 'latex'
supported_image_types = ['application/pdf', 'image/png', 'image/jpeg']
- usepackages = []
def init(self):
self.docnames = []
self.document_data = []
+ self.usepackages = []
texescape.init()
- self.check_options()
-
- def check_options(self):
- if self.config.latex_toplevel_sectioning not in (None, 'part', 'chapter', 'section'):
- self.warn('invalid latex_toplevel_sectioning, ignored: %s' %
- self.config.latex_top_sectionlevel)
- self.config.latex_top_sectionlevel = None
-
- if self.config.latex_use_parts:
- warnings.warn('latex_use_parts will be removed at Sphinx-1.5. '
- 'Use latex_toplevel_sectioning instead.',
- DeprecationWarning)
-
- if self.config.latex_toplevel_sectioning:
- self.warn('latex_use_parts conflicts with latex_toplevel_sectioning, ignored.')
def get_outdated_docs(self):
return 'all documents' # for now
@@ -92,6 +78,16 @@ class LaTeXBuilder(Builder):
docname = docname[:-5]
self.titles.append((docname, entry[2]))
+ def write_stylesheet(self):
+ highlighter = highlighting.PygmentsBridge(
+ 'latex', self.config.pygments_style, self.config.trim_doctest_flags)
+ stylesheet = path.join(self.outdir, 'sphinxhighlight.sty')
+ with open(stylesheet, 'w') as f:
+ f.write('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n')
+ f.write('\\ProvidesPackage{sphinxhighlight}'
+ '[2016/05/29 stylesheet for highlighting with pygments]\n\n')
+ f.write(highlighter.get_stylesheet())
+
def write(self, *ignored):
docwriter = LaTeXWriter(self)
docsettings = OptionParser(
@@ -100,6 +96,7 @@ class LaTeXBuilder(Builder):
read_config_files=True).get_default_values()
self.init_document_data()
+ self.write_stylesheet()
for entry in self.document_data:
docname, targetname, title, author, docclass = entry[:5]
@@ -192,33 +189,118 @@ class LaTeXBuilder(Builder):
self.info(bold('copying images...'), nonl=1)
for src, dest in iteritems(self.images):
self.info(' '+src, nonl=1)
- copyfile(path.join(self.srcdir, src),
- path.join(self.outdir, dest))
+ copy_asset_file(path.join(self.srcdir, src),
+ path.join(self.outdir, dest))
self.info()
# copy TeX support files from texinputs
+ context = {'latex_engine': self.config.latex_engine}
self.info(bold('copying TeX support files...'))
staticdirname = path.join(package_dir, 'texinputs')
for filename in os.listdir(staticdirname):
if not filename.startswith('.'):
- copyfile(path.join(staticdirname, filename),
- path.join(self.outdir, filename))
+ copy_asset_file(path.join(staticdirname, filename),
+ self.outdir, context=context)
# copy additional files
if self.config.latex_additional_files:
self.info(bold('copying additional files...'), nonl=1)
for filename in self.config.latex_additional_files:
self.info(' '+filename, nonl=1)
- copyfile(path.join(self.confdir, filename),
- path.join(self.outdir, path.basename(filename)))
+ copy_asset_file(path.join(self.confdir, filename), self.outdir)
self.info()
# the logo is handled differently
if self.config.latex_logo:
- logobase = path.basename(self.config.latex_logo)
- logotarget = path.join(self.outdir, logobase)
if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
raise SphinxError('logo file %r does not exist' % self.config.latex_logo)
- elif not path.isfile(logotarget):
- copyfile(path.join(self.confdir, self.config.latex_logo), logotarget)
+ else:
+ copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
self.info('done')
+
+
+def validate_config_values(app):
+ if app.config.latex_toplevel_sectioning not in (None, 'part', 'chapter', 'section'):
+ app.warn('invalid latex_toplevel_sectioning, ignored: %s' %
+ app.config.latex_toplevel_sectioning)
+ app.config.latex_toplevel_sectioning = None
+
+ if app.config.latex_use_parts:
+ if app.config.latex_toplevel_sectioning:
+ app.warn('latex_use_parts conflicts with latex_toplevel_sectioning, ignored.')
+ else:
+ app.warn('latex_use_parts is deprecated. Use latex_toplevel_sectioning instead.')
+ app.config.latex_toplevel_sectioning = 'parts'
+
+ if app.config.latex_use_modindex is not True: # changed by user
+ app.warn('latex_use_modeindex is deprecated. Use latex_domain_indices instead.')
+
+ if app.config.latex_preamble:
+ if app.config.latex_elements.get('preamble'):
+ app.warn("latex_preamble conflicts with latex_elements['preamble'], ignored.")
+ else:
+ app.warn("latex_preamble is deprecated. Use latex_elements['preamble'] instead.")
+ app.config.latex_elements['preamble'] = app.config.latex_preamble
+
+ if app.config.latex_paper_size != 'letter':
+ if app.config.latex_elements.get('papersize'):
+ app.warn("latex_paper_size conflicts with latex_elements['papersize'], ignored.")
+ else:
+ app.warn("latex_paper_size is deprecated. "
+ "Use latex_elements['papersize'] instead.")
+ if app.config.latex_paper_size:
+ app.config.latex_elements['papersize'] = app.config.latex_paper_size + 'paper'
+
+ if app.config.latex_font_size != '10pt':
+ if app.config.latex_elements.get('pointsize'):
+ app.warn("latex_font_size conflicts with latex_elements['pointsize'], ignored.")
+ else:
+ app.warn("latex_font_size is deprecated. Use latex_elements['pointsize'] instead.")
+ app.config.latex_elements['pointsize'] = app.config.latex_font_size
+
+ if 'footer' in app.config.latex_elements:
+ if 'postamble' in app.config.latex_elements:
+ app.warn("latex_elements['footer'] conflicts with "
+ "latex_elements['postamble'], ignored.")
+ else:
+ app.warn("latex_elements['footer'] is deprecated. "
+ "Use latex_elements['preamble'] instead.")
+ app.config.latex_elements['postamble'] = app.config.latex_elements['footer']
+
+
+def setup(app):
+ app.add_builder(LaTeXBuilder)
+ app.connect('builder-inited', validate_config_values)
+
+ app.add_config_value('latex_engine',
+ lambda self: 'pdflatex' if self.language != 'ja' else 'platex',
+ None,
+ ENUM('pdflatex', 'xelatex', 'lualatex', 'platex'))
+ app.add_config_value('latex_documents',
+ lambda self: [(self.master_doc, make_filename(self.project) + '.tex',
+ self.project, '', 'manual')],
+ None)
+ app.add_config_value('latex_logo', None, None, string_classes)
+ app.add_config_value('latex_appendices', [], None)
+ app.add_config_value('latex_keep_old_macro_names', True, None)
+ # now deprecated - use latex_toplevel_sectioning
+ app.add_config_value('latex_use_parts', False, None)
+ app.add_config_value('latex_toplevel_sectioning', None, None, [str])
+ app.add_config_value('latex_use_modindex', True, None) # deprecated
+ app.add_config_value('latex_domain_indices', True, None, [list])
+ app.add_config_value('latex_show_urls', 'no', None)
+ app.add_config_value('latex_show_pagerefs', False, None)
+ # paper_size and font_size are still separate values
+ # so that you can give them easily on the command line
+ app.add_config_value('latex_paper_size', 'letter', None)
+ app.add_config_value('latex_font_size', '10pt', None)
+ app.add_config_value('latex_elements', {}, None)
+ app.add_config_value('latex_additional_files', [], None)
+
+ japanese_default = {'manual': 'jsbook',
+ 'howto': 'jreport'}
+ app.add_config_value('latex_docclass',
+ lambda self: japanese_default if self.language == 'ja' else {},
+ None)
+ # now deprecated - use latex_elements
+ app.add_config_value('latex_preamble', '', None)
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index bb7967e83..f49f4f9a3 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -15,10 +15,9 @@ import codecs
import threading
from os import path
+from requests.exceptions import HTTPError
from six.moves import queue
-from six.moves.urllib.request import build_opener, Request, HTTPRedirectHandler
from six.moves.urllib.parse import unquote
-from six.moves.urllib.error import HTTPError
from six.moves.html_parser import HTMLParser
from docutils import nodes
@@ -36,34 +35,7 @@ from sphinx.builders import Builder
from sphinx.util import encode_uri
from sphinx.util.console import purple, red, darkgreen, darkgray, \
darkred, turquoise
-from sphinx.util.pycompat import TextIOWrapper
-
-
-class RedirectHandler(HTTPRedirectHandler):
- """A RedirectHandler that records the redirect code we got."""
-
- def redirect_request(self, req, fp, code, msg, headers, newurl):
- new_req = HTTPRedirectHandler.redirect_request(self, req, fp, code,
- msg, headers, newurl)
- req.redirect_code = code
- return new_req
-
-
-# create an opener that will simulate a browser user-agent
-opener = build_opener(RedirectHandler)
-opener.addheaders = [('User-agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) '
- 'Gecko/20100101 Firefox/25.0')]
-
-
-class HeadRequest(Request):
- """Subclass of urllib2.Request that sends a HEAD request."""
- def __init__(self, *args, **kwargs):
- Request.__init__(self, *args, **kwargs)
- # we do not parse the response in HEAD, so accepting anything is okay
- self.headers['Accept-encoding'] = '*'
-
- def get_method(self):
- return 'HEAD'
+from sphinx.util.requests import requests, useragent_header, is_ssl_error
class AnchorCheckParser(HTMLParser):
@@ -79,20 +51,21 @@ class AnchorCheckParser(HTMLParser):
for key, value in attrs:
if key in ('id', 'name') and value == self.search_anchor:
self.found = True
+ break
-def check_anchor(f, anchor):
- """Reads HTML data from a filelike object 'f' searching for *anchor*.
+def check_anchor(response, anchor):
+ """Reads HTML data from a response object `response` searching for `anchor`.
Returns True if anchor was found, False otherwise.
"""
parser = AnchorCheckParser(anchor)
try:
- # Read file in chunks of 8192 bytes. If we find a matching anchor, we
- # break the loop early in hopes not to have to download the whole thing.
- chunk = f.read(8192)
- while chunk and not parser.found:
+ # Read file in chunks. If we find a matching anchor, we break
+ # the loop early in hopes not to have to download the whole thing.
+ for chunk in response.iter_content(chunk_size=4096, decode_unicode=True):
parser.feed(chunk)
- chunk = f.read(8192)
+ if parser.found:
+ break
parser.close()
except HTMLParseError:
# HTMLParser is usually pretty good with sloppy HTML, but it tends to
@@ -101,17 +74,6 @@ def check_anchor(f, anchor):
return parser.found
-def get_content_charset(f):
- content_type = f.headers.get('content-type')
- if content_type:
- params = (p.strip() for p in content_type.split(';')[1:])
- for param in params:
- if param.startswith('charset='):
- return param[8:]
-
- return None
-
-
class CheckExternalLinksBuilder(Builder):
"""
Checks for broken external links.
@@ -120,9 +82,12 @@ class CheckExternalLinksBuilder(Builder):
def init(self):
self.to_ignore = [re.compile(x) for x in self.app.config.linkcheck_ignore]
+ self.anchors_ignore = [re.compile(x)
+ for x in self.app.config.linkcheck_anchors_ignore]
self.good = set()
self.broken = {}
self.redirected = {}
+ self.headers = dict(useragent_header)
# set a timeout for non-responding servers
socket.setdefaulttimeout(5.0)
# create output file
@@ -143,10 +108,16 @@ class CheckExternalLinksBuilder(Builder):
if self.app.config.linkcheck_timeout:
kwargs['timeout'] = self.app.config.linkcheck_timeout
+ kwargs['allow_redirects'] = True
+
def check_uri():
# split off anchor
if '#' in uri:
req_url, anchor = uri.split('#', 1)
+ for rex in self.anchors_ignore:
+ if rex.match(anchor):
+ anchor = None
+ break
else:
req_url = uri
anchor = None
@@ -158,54 +129,46 @@ class CheckExternalLinksBuilder(Builder):
req_url = encode_uri(req_url)
try:
- if anchor and self.app.config.linkcheck_anchors and \
- not anchor.startswith('!'):
+ if anchor and self.app.config.linkcheck_anchors:
# Read the whole document and see if #anchor exists
- # (Anchors starting with ! are ignored since they are
- # commonly used for dynamic pages)
- req = Request(req_url)
- f = opener.open(req, **kwargs)
- encoding = 'utf-8'
- if hasattr(f.headers, 'get_content_charset'):
- encoding = f.headers.get_content_charset() or encoding
- else:
- encoding = get_content_charset(f) or encoding
- found = check_anchor(TextIOWrapper(f, encoding),
- unquote(anchor))
- f.close()
+ response = requests.get(req_url, stream=True, headers=self.headers,
+ **kwargs)
+ found = check_anchor(response, unquote(anchor))
if not found:
raise Exception("Anchor '%s' not found" % anchor)
else:
try:
- # try a HEAD request, which should be easier on
+ # try a HEAD request first, which should be easier on
# the server and the network
- req = HeadRequest(req_url)
- f = opener.open(req, **kwargs)
- f.close()
+ response = requests.head(req_url, headers=self.headers, **kwargs)
+ response.raise_for_status()
except HTTPError as err:
- if err.code not in (403, 405):
- raise
- # retry with GET if that fails, some servers
- # don't like HEAD requests and reply with 403 or 405
- req = Request(req_url)
- f = opener.open(req, **kwargs)
- f.close()
+ # retry with GET request if that fails, some servers
+ # don't like HEAD requests.
+ response = requests.get(req_url, stream=True, headers=self.headers,
+ **kwargs)
+ response.raise_for_status()
except HTTPError as err:
- if err.code == 401:
+ if err.response.status_code == 401:
# We'll take "Unauthorized" as working.
return 'working', ' - unauthorized', 0
else:
return 'broken', str(err), 0
except Exception as err:
- return 'broken', str(err), 0
- if f.url.rstrip('/') == req_url.rstrip('/'):
+ if is_ssl_error(err):
+ return 'ignored', str(err), 0
+ else:
+ return 'broken', str(err), 0
+ if response.url.rstrip('/') == req_url.rstrip('/'):
return 'working', '', 0
else:
- new_url = f.url
+ new_url = response.url
if anchor:
new_url += '#' + anchor
- code = getattr(req, 'redirect_code', 0)
+ # history contains any redirects, get last
+ if response.history:
+ code = response.history[-1].status_code
return 'redirected', new_url, code
def check():
@@ -255,7 +218,10 @@ class CheckExternalLinksBuilder(Builder):
if lineno:
self.info('(line %4d) ' % lineno, nonl=1)
if status == 'ignored':
- self.info(darkgray('-ignored- ') + uri)
+ if info:
+ self.info(darkgray('-ignored- ') + uri + ': ' + info)
+ else:
+ self.info(darkgray('-ignored- ') + uri)
elif status == 'local':
self.info(darkgray('-local- ') + uri)
self.write_entry('local', docname, lineno, uri)
@@ -264,7 +230,7 @@ class CheckExternalLinksBuilder(Builder):
elif status == 'broken':
self.write_entry('broken', docname, lineno, uri + ': ' + info)
if self.app.quiet or self.app.warningiserror:
- self.warn('broken link: %s' % uri,
+ self.warn('broken link: %s (%s)' % (uri, info),
'%s:%s' % (self.env.doc2path(docname), lineno))
else:
self.info(red('broken ') + uri + red(' - ' + info))
@@ -321,3 +287,16 @@ class CheckExternalLinksBuilder(Builder):
def finish(self):
for worker in self.workers:
self.wqueue.put((None, None, None), False)
+
+
+def setup(app):
+ app.add_builder(CheckExternalLinksBuilder)
+
+ app.add_config_value('linkcheck_ignore', [], None)
+ app.add_config_value('linkcheck_retries', 1, None)
+ app.add_config_value('linkcheck_timeout', None, None, [int])
+ app.add_config_value('linkcheck_workers', 5, None)
+ app.add_config_value('linkcheck_anchors', True, None)
+ # Anchors starting with ! are ignored since they are
+ # commonly used for dynamic pages
+ app.add_config_value('linkcheck_anchors_ignore', ["^!"], None)
diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py
index a2e75f9f7..248ed40b2 100644
--- a/sphinx/builders/manpage.py
+++ b/sphinx/builders/manpage.py
@@ -19,6 +19,7 @@ from sphinx import addnodes
from sphinx.builders import Builder
from sphinx.environment import NoUri
from sphinx.util.nodes import inline_all_toctrees
+from sphinx.util.osutil import make_filename
from sphinx.util.console import bold, darkgreen
from sphinx.writers.manpage import ManualPageWriter
@@ -88,3 +89,13 @@ class ManualPageBuilder(Builder):
def finish(self):
pass
+
+
+def setup(app):
+ app.add_builder(ManualPageBuilder)
+
+ app.add_config_value('man_pages',
+ lambda self: [(self.master_doc, make_filename(self.project).lower(),
+ '%s %s' % (self.project, self.release), [], 1)],
+ None)
+ app.add_config_value('man_show_urls', False, None)
diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py
index e02985b94..c53b56657 100644
--- a/sphinx/builders/qthelp.py
+++ b/sphinx/builders/qthelp.py
@@ -21,6 +21,7 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.util import force_decode
+from sphinx.util.osutil import make_filename
from sphinx.util.pycompat import htmlescape
@@ -104,8 +105,14 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
# don't add links
add_permalinks = False
+
# don't add sidebar etc.
embedded = True
+ # disable download role
+ download_support = False
+
+ # don't generate the search index or include the search page
+ search = False
def init(self):
StandaloneHTMLBuilder.init(self)
@@ -114,6 +121,9 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
self.link_suffix = '.html'
# self.config.html_style = 'traditional.css'
+ def get_theme_config(self):
+ return self.config.qthelp_theme, self.config.qthelp_theme_options
+
def handle_finish(self):
self.build_qhp(self.outdir, self.config.qthelp_basename)
@@ -180,8 +190,7 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
nspace = nspace.lower()
# write the project file
- f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8') as f:
f.write(project_template % {
'outname': htmlescape(outname),
'title': htmlescape(self.config.html_title),
@@ -192,23 +201,18 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
'sections': sections,
'keywords': keywords,
'files': projectfiles})
- finally:
- f.close()
homepage = 'qthelp://' + posixpath.join(
nspace, 'doc', self.get_target_uri(self.config.master_doc))
startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html')
self.info('writing collection project file...')
- f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8') as f:
f.write(collection_template % {
'outname': htmlescape(outname),
'title': htmlescape(self.config.html_short_title),
'homepage': htmlescape(homepage),
'startpage': htmlescape(startpage)})
- finally:
- f.close()
def isdocnode(self, node):
if not isinstance(node, nodes.list_item):
@@ -297,3 +301,12 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
keywords.extend(self.build_keywords(subitem[0], subitem[1], []))
return keywords
+
+
+def setup(app):
+ app.setup_extension('sphinx.builders.html')
+ app.add_builder(QtHelpBuilder)
+
+ app.add_config_value('qthelp_basename', lambda self: make_filename(self.project), None)
+ app.add_config_value('qthelp_theme', 'nonav', 'html')
+ app.add_config_value('qthelp_theme_options', {}, 'html')
diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py
index dec278c86..f070840b6 100644
--- a/sphinx/builders/texinfo.py
+++ b/sphinx/builders/texinfo.py
@@ -22,7 +22,7 @@ from sphinx.locale import _
from sphinx.builders import Builder
from sphinx.environment import NoUri
from sphinx.util.nodes import inline_all_toctrees
-from sphinx.util.osutil import SEP, copyfile
+from sphinx.util.osutil import SEP, copyfile, make_filename
from sphinx.util.console import bold, darkgreen
from sphinx.writers.texinfo import TexinfoWriter
@@ -220,11 +220,25 @@ class TexinfoBuilder(Builder):
fn = path.join(self.outdir, 'Makefile')
self.info(fn, nonl=1)
try:
- mkfile = open(fn, 'w')
- try:
+ with open(fn, 'w') as mkfile:
mkfile.write(TEXINFO_MAKEFILE)
- finally:
- mkfile.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (fn, err))
self.info(' done')
+
+
+def setup(app):
+ app.add_builder(TexinfoBuilder)
+
+ app.add_config_value('texinfo_documents',
+ lambda self: [(self.master_doc, make_filename(self.project).lower(),
+ self.project, '', make_filename(self.project),
+ 'The %s reference manual.' %
+ make_filename(self.project),
+ 'Python')],
+ None)
+ app.add_config_value('texinfo_appendices', [], None)
+ app.add_config_value('texinfo_elements', {}, None)
+ app.add_config_value('texinfo_domain_indices', True, None, [list])
+ app.add_config_value('texinfo_show_urls', 'footnote', None)
+ app.add_config_value('texinfo_no_detailmenu', False, None)
diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py
index 85da4a1a2..2daf8b043 100644
--- a/sphinx/builders/text.py
+++ b/sphinx/builders/text.py
@@ -60,13 +60,17 @@ class TextBuilder(Builder):
outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix)
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', 'utf-8')
- try:
+ with codecs.open(outfilename, 'w', 'utf-8') as f:
f.write(self.writer.output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
def finish(self):
pass
+
+
+def setup(app):
+ app.add_builder(TextBuilder)
+
+ app.add_config_value('text_sectionchars', '*=-~"+`', 'env')
+ app.add_config_value('text_newlines', 'unix', 'env')
diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py
index 843b0899e..d8ff5ad8d 100644
--- a/sphinx/builders/websupport.py
+++ b/sphinx/builders/websupport.py
@@ -165,3 +165,7 @@ class WebSupportBuilder(PickleHTMLBuilder):
def dump_search_index(self):
self.indexer.finish_indexing()
+
+
+def setup(app):
+ app.add_builder(WebSupportBuilder)
diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py
index 91cb273f5..e0e33312c 100644
--- a/sphinx/builders/xml.py
+++ b/sphinx/builders/xml.py
@@ -77,11 +77,8 @@ class XMLBuilder(Builder):
outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix)
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', 'utf-8')
- try:
+ with codecs.open(outfilename, 'w', 'utf-8') as f:
f.write(self.writer.output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
@@ -98,3 +95,10 @@ class PseudoXMLBuilder(XMLBuilder):
out_suffix = '.pseudoxml'
_writer_class = PseudoXMLWriter
+
+
+def setup(app):
+ app.add_builder(XMLBuilder)
+ app.add_builder(PseudoXMLBuilder)
+
+ app.add_config_value('xml_pretty', True, 'env')