summaryrefslogtreecommitdiff
path: root/sphinx/builders
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2010-05-23 16:30:04 +0200
committerGeorg Brandl <georg@python.org>2010-05-23 16:30:04 +0200
commitc676daa50d4c09585e3e8cfadcb9bee5b688d2ba (patch)
treec262d416cbb324710f57379f7b2c825d17357c87 /sphinx/builders
parent38e3c0b390fafd719515d8979786727ba9bed1fb (diff)
parent569ab21949cd3fc97f351e5e668303dd80c14680 (diff)
downloadsphinx-c676daa50d4c09585e3e8cfadcb9bee5b688d2ba.tar.gz
merge with 0.6
Diffstat (limited to 'sphinx/builders')
-rw-r--r--sphinx/builders/__init__.py104
-rw-r--r--sphinx/builders/changes.py10
-rw-r--r--sphinx/builders/devhelp.py133
-rw-r--r--sphinx/builders/epub.py441
-rw-r--r--sphinx/builders/html.py522
-rw-r--r--sphinx/builders/htmlhelp.py6
-rw-r--r--sphinx/builders/latex.py29
-rw-r--r--sphinx/builders/linkcheck.py2
-rw-r--r--sphinx/builders/manpage.py92
-rw-r--r--sphinx/builders/qthelp.py9
-rw-r--r--sphinx/builders/text.py2
11 files changed, 1046 insertions, 304 deletions
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index 5d75a886..e345d570 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -10,14 +10,11 @@
"""
import os
-import gettext
from os import path
from docutils import nodes
-from sphinx import package_dir, locale
-from sphinx.util import SEP, ENOENT, relative_uri
-from sphinx.environment import BuildEnvironment
+from sphinx.util.osutil import SEP, relative_uri
from sphinx.util.console import bold, purple, darkgreen, term_width_line
# side effect: registers roles and directives
@@ -25,9 +22,6 @@ from sphinx import roles
from sphinx import directives
-ENV_PICKLE_FILENAME = 'environment.pickle'
-
-
class Builder(object):
"""
Builds target formats from the reST sources.
@@ -38,7 +32,8 @@ class Builder(object):
# builder's output format, or '' if no document output is produced
format = ''
- def __init__(self, app, env=None, freshenv=False):
+ def __init__(self, app):
+ self.env = app.env
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@@ -50,21 +45,15 @@ class Builder(object):
self.warn = app.warn
self.info = app.info
self.config = app.config
-
- self.load_i18n()
+ self.tags = app.tags
+ self.tags.add(self.format)
# images that need to be copied over (source -> dest)
self.images = {}
- # if None, this is set in load_env()
- self.env = env
- self.freshenv = freshenv
-
self.init()
- self.load_env()
# helper methods
-
def init(self):
"""
Load necessary templates and perform initialization. The default
@@ -167,62 +156,6 @@ class Builder(object):
# build methods
- def load_i18n(self):
- """
- Load translated strings from the configured localedirs if
- enabled in the configuration.
- """
- self.translator = None
- if self.config.language is not None:
- self.info(bold('loading translations [%s]... ' %
- self.config.language), nonl=True)
- # the None entry is the system's default locale path
- locale_dirs = [None, path.join(package_dir, 'locale')] + \
- [path.join(self.srcdir, x) for x in self.config.locale_dirs]
- for dir_ in locale_dirs:
- try:
- trans = gettext.translation('sphinx', localedir=dir_,
- languages=[self.config.language])
- if self.translator is None:
- self.translator = trans
- else:
- self.translator._catalog.update(trans._catalog)
- except Exception:
- # Language couldn't be found in the specified path
- pass
- if self.translator is not None:
- self.info('done')
- else:
- self.info('locale not available')
- if self.translator is None:
- self.translator = gettext.NullTranslations()
- self.translator.install(unicode=True)
- locale.init() # translate common labels
-
- def load_env(self):
- """Set up the build environment."""
- if self.env:
- return
- if not self.freshenv:
- try:
- self.info(bold('loading pickled environment... '), nonl=True)
- self.env = BuildEnvironment.frompickle(self.config,
- path.join(self.doctreedir, ENV_PICKLE_FILENAME))
- self.info('done')
- except Exception, err:
- if type(err) is IOError and err.errno == ENOENT:
- self.info('not found')
- else:
- self.info('failed: %s' % err)
- self.env = BuildEnvironment(self.srcdir, self.doctreedir,
- self.config)
- self.env.find_files(self.config)
- else:
- self.env = BuildEnvironment(self.srcdir, self.doctreedir,
- self.config)
- self.env.find_files(self.config)
- self.env.set_warnfunc(self.warn)
-
def build_all(self):
"""Build all source files."""
self.build(None, summary='all source files', method='all')
@@ -302,6 +235,7 @@ class Builder(object):
if updated_docnames:
# save the environment
+ from sphinx.application import ENV_PICKLE_FILENAME
self.info(bold('pickling environment... '), nonl=True)
self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.info('done')
@@ -380,15 +314,19 @@ class Builder(object):
BUILTIN_BUILDERS = {
- 'html': ('html', 'StandaloneHTMLBuilder'),
- 'dirhtml': ('html', 'DirectoryHTMLBuilder'),
- 'pickle': ('html', 'PickleHTMLBuilder'),
- 'json': ('html', 'JSONHTMLBuilder'),
- 'web': ('html', 'PickleHTMLBuilder'),
- 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'),
- 'qthelp': ('qthelp', 'QtHelpBuilder'),
- 'latex': ('latex', 'LaTeXBuilder'),
- 'text': ('text', 'TextBuilder'),
- 'changes': ('changes', 'ChangesBuilder'),
- 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'),
+ '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'),
+ 'epub': ('epub', 'EpubBuilder'),
+ 'latex': ('latex', 'LaTeXBuilder'),
+ 'text': ('text', 'TextBuilder'),
+ 'man': ('manpage', 'ManualPageBuilder'),
+ 'changes': ('changes', 'ChangesBuilder'),
+ 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'),
}
diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py
index 1844354a..980ed760 100644
--- a/sphinx/builders/changes.py
+++ b/sphinx/builders/changes.py
@@ -14,9 +14,11 @@ from os import path
from cgi import escape
from sphinx import package_dir
-from sphinx.util import ensuredir, os_path, copy_static_entry
+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
@@ -93,6 +95,7 @@ class ChangesBuilder(Builder):
'libchanges': sorted(libchanges.iteritems()),
'apichanges': sorted(apichanges),
'otherchanges': sorted(otherchanges.iteritems()),
+ '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')
@@ -138,11 +141,10 @@ class ChangesBuilder(Builder):
self.theme.get_options({}).iteritems())
copy_static_entry(path.join(package_dir, 'themes', 'default',
'static', 'default.css_t'),
- path.join(self.outdir, 'default.css_t'),
- self, themectx)
+ self.outdir, self, themectx)
copy_static_entry(path.join(package_dir, 'themes', 'basic',
'static', 'basic.css'),
- path.join(self.outdir, 'basic.css'), self)
+ self.outdir, self)
def hl(self, text, version):
text = escape(text)
diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py
new file mode 100644
index 00000000..a5a0f280
--- /dev/null
+++ b/sphinx/builders/devhelp.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.builders.devhelp
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Build HTML documentation and Devhelp_ support files.
+
+ .. _Devhelp: http://live.gnome.org/devhelp
+
+ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+from os import path
+
+from docutils import nodes
+
+from sphinx import addnodes
+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)
+
+
+class DevhelpBuilder(StandaloneHTMLBuilder):
+ """
+ Builder that also outputs GNOME Devhelp file.
+
+ """
+ name = 'devhelp'
+
+ # don't copy the reST source
+ copysource = False
+ supported_image_types = ['image/png', 'image/gif', 'image/jpeg']
+
+ # don't add links
+ add_permalinks = False
+ # don't add sidebar etc.
+ embedded = True
+
+ def init(self):
+ StandaloneHTMLBuilder.init(self)
+ self.out_suffix = '.html'
+
+ def handle_finish(self):
+ self.build_devhelp(self.outdir, self.config.devhelp_basename)
+
+ def build_devhelp(self, outdir, outname):
+ self.info('dumping devhelp index...')
+
+ # Basic info
+ root = etree.Element('book',
+ title=self.config.html_title,
+ name=self.config.project,
+ link="index.html",
+ version=self.config.version)
+ tree = etree.ElementTree(root)
+
+ # TOC
+ chapters = etree.SubElement(root, 'chapters')
+
+ tocdoc = self.env.get_and_resolve_doctree(
+ self.config.master_doc, self, prune_toctrees=False)
+
+ def write_toc(node, parent):
+ if isinstance(node, addnodes.compact_paragraph) or \
+ isinstance(node, nodes.bullet_list):
+ for subnode in node:
+ write_toc(subnode, parent)
+ elif isinstance(node, nodes.list_item):
+ item = etree.SubElement(parent, 'sub')
+ for subnode in node:
+ write_toc(subnode, item)
+ elif isinstance(node, nodes.reference):
+ parent.attrib['link'] = node['refuri']
+ parent.attrib['name'] = node.astext().encode('utf-8')
+
+ def istoctree(node):
+ return isinstance(node, addnodes.compact_paragraph) and \
+ node.has_key('toctree')
+
+ for node in tocdoc.traverse(istoctree):
+ write_toc(node, chapters)
+
+ # Index
+ functions = etree.SubElement(root, 'functions')
+ index = self.env.create_index(self)
+
+ def write_index(title, refs, subitems):
+ if len(refs) == 0:
+ pass
+ elif len(refs) == 1:
+ etree.SubElement(functions, 'function',
+ name=title, link=refs[0])
+ else:
+ for i, ref in enumerate(refs):
+ etree.SubElement(functions, 'function',
+ name="[%d] %s" % (i, title),
+ link=ref)
+
+ if subitems:
+ parent_title = re.sub(r'\s*\(.*\)\s*$', '', title)
+ for subitem in subitems:
+ write_index("%s %s" % (parent_title, subitem[0]),
+ subitem[1], [])
+
+ for (key, group) in index:
+ for title, (refs, subitems) in group:
+ write_index(title, refs, subitems)
+
+ # Dump the XML file
+ f = comp_open(path.join(outdir, outname + '.devhelp'), 'w')
+ try:
+ tree.write(f)
+ finally:
+ f.close()
diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py
new file mode 100644
index 00000000..9767391e
--- /dev/null
+++ b/sphinx/builders/epub.py
@@ -0,0 +1,441 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.builders.epub
+ ~~~~~~~~~~~~~~~~~~~~
+
+ Build epub files.
+ Originally derived from qthelp.py.
+
+ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+import codecs
+from os import path
+import zipfile
+
+from docutils import nodes
+from docutils.transforms import Transform
+
+from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.util.osutil import EEXIST
+
+
+# (Fragment) templates from which the metainfo files content.opf, toc.ncx,
+# mimetype, and META-INF/container.xml are created.
+# This template section also defines strings that are embedded in the html
+# output but that may be customized by (re-)setting module attributes,
+# e.g. from conf.py.
+
+_mimetype_template = 'application/epub+zip' # no EOL!
+
+_container_template = u'''\
+<?xml version="1.0" encoding="UTF-8"?>
+<container version="1.0"
+ xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
+ <rootfiles>
+ <rootfile full-path="content.opf"
+ media-type="application/oebps-package+xml"/>
+ </rootfiles>
+</container>
+'''
+
+_toc_template = u'''\
+<?xml version="1.0"?>
+<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
+ <head>
+ <meta name="dtb:uid" content="%(uid)s"/>
+ <meta name="dtb:depth" content="%(level)d"/>
+ <meta name="dtb:totalPageCount" content="0"/>
+ <meta name="dtb:maxPageNumber" content="0"/>
+ </head>
+ <docTitle>
+ <text>%(title)s</text>
+ </docTitle>
+ <navMap>
+%(navpoints)s
+ </navMap>
+</ncx>
+'''
+
+_navpoint_template = u'''\
+%(indent)s <navPoint id="%(navpoint)s" playOrder="%(playorder)d">
+%(indent)s <navLabel>
+%(indent)s <text>%(text)s</text>
+%(indent)s </navLabel>
+%(indent)s <content src="%(refuri)s" />
+%(indent)s </navPoint>'''
+
+_navpoint_indent = ' '
+_navPoint_template = 'navPoint%d'
+
+_content_template = u'''\
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="http://www.idpf.org/2007/opf" version="2.0"
+ unique-identifier="%(uid)s">
+ <metadata xmlns:opf="http://www.idpf.org/2007/opf"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <dc:language>%(lang)s</dc:language>
+ <dc:title>%(title)s</dc:title>
+ <dc:creator opf:role="aut">%(author)s</dc:creator>
+ <dc:publisher>%(publisher)s</dc:publisher>
+ <dc:rights>%(copyright)s</dc:rights>
+ <dc:identifier id="%(uid)s" opf:scheme="%(scheme)s">%(id)s</dc:identifier>
+ </metadata>
+ <manifest>
+ <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
+%(files)s
+ </manifest>
+ <spine toc="ncx">
+%(spine)s
+ </spine>
+</package>
+'''
+
+_file_template = u'''\
+ <item id="%(id)s"
+ href="%(href)s"
+ media-type="%(media_type)s" />'''
+
+_spine_template = u'''\
+ <itemref idref="%(idref)s" />'''
+
+_toctree_template = u'toctree-l%d'
+
+_link_target_template = u' [%(uri)s]'
+
+_css_link_target_class = u'link-target'
+
+_media_types = {
+ '.html': 'application/xhtml+xml',
+ '.css': 'text/css',
+ '.png': 'image/png',
+ '.gif': 'image/gif',
+ '.svg': 'image/svg+xml',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.otf': 'application/x-font-otf',
+ '.ttf': 'application/x-font-ttf',
+}
+
+
+# The transform to show link targets
+
+class VisibleLinksTransform(Transform):
+ """
+ Add the link target of referances to the text, unless it is already
+ present in the description.
+ """
+
+ # This transform must run after the references transforms
+ default_priority = 680
+
+ def apply(self):
+ for ref in self.document.traverse(nodes.reference):
+ uri = ref.get('refuri', '')
+ if ( uri.startswith('http:') or uri.startswith('https:') or \
+ uri.startswith('ftp:') ) and uri not in ref.astext():
+ uri = _link_target_template % {'uri': uri}
+ if uri:
+ idx = ref.parent.index(ref) + 1
+ link = nodes.inline(uri, uri)
+ link['classes'].append(_css_link_target_class)
+ ref.parent.insert(idx, link)
+
+
+# The epub publisher
+
+class EpubBuilder(StandaloneHTMLBuilder):
+ """Builder that outputs epub files.
+
+ It creates the metainfo files container.opf, toc.ncx, mimetype, and
+ META-INF/container.xml. Afterwards, all necessary files are zipped to an
+ epub file.
+ """
+ name = 'epub'
+
+ # don't copy the reST source
+ copysource = False
+ supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
+ 'image/jpeg']
+
+ # don't add links
+ add_permalinks = False
+ # don't add sidebar etc.
+ embedded = True
+
+ def init(self):
+ StandaloneHTMLBuilder.init(self)
+ # the output files for epub must be .html only
+ self.out_suffix = '.html'
+ self.playorder = 0
+ self.app.add_transform(VisibleLinksTransform)
+
+ def get_theme_config(self):
+ return self.config.epub_theme, {}
+
+ # generic support functions
+ def make_id(self, name):
+ """Replace all characters not allowed for (X)HTML ids."""
+ return name.replace('/', '_').replace(' ', '')
+
+ def esc(self, name):
+ """Replace all characters not allowed in text an attribute values."""
+ # Like cgi.escape, but also replace apostrophe
+ name = name.replace('&', '&amp;')
+ name = name.replace('<', '&lt;')
+ name = name.replace('>', '&gt;')
+ name = name.replace('"', '&quot;')
+ name = name.replace('\'', '&apos;')
+ return name
+
+ def get_refnodes(self, doctree, result):
+ """Collect section titles, their depth in the toc and the refuri."""
+ # XXX: is there a better way than checking the attribute
+ # toctree-l[1-8] on the parent node?
+ if isinstance(doctree, nodes.reference):
+ classes = doctree.parent.attributes['classes']
+ level = 1
+ for l in range(8, 0, -1): # or range(1, 8)?
+ if (_toctree_template % l) in classes:
+ level = l
+ result.append({
+ 'level': level,
+ 'refuri': self.esc(doctree['refuri']),
+ 'text': self.esc(doctree.astext())
+ })
+ else:
+ for elem in doctree.children:
+ result = self.get_refnodes(elem, result)
+ return result
+
+ def get_toc(self):
+ """Get the total table of contents, containg the master_doc
+ and pre and post files not managed by sphinx.
+ """
+ doctree = self.env.get_and_resolve_doctree(self.config.master_doc,
+ self, prune_toctrees=False)
+ self.refnodes = self.get_refnodes(doctree, [])
+ self.refnodes.insert(0, {
+ 'level': 1,
+ 'refuri': self.esc(self.config.master_doc + '.html'),
+ 'text': self.esc(self.env.titles[self.config.master_doc].astext())
+ })
+ for file, text in reversed(self.config.epub_pre_files):
+ self.refnodes.insert(0, {
+ 'level': 1,
+ 'refuri': self.esc(file + '.html'),
+ 'text': self.esc(text)
+ })
+ for file, text in self.config.epub_post_files:
+ self.refnodes.append({
+ 'level': 1,
+ 'refuri': self.esc(file + '.html'),
+ 'text': self.esc(text)
+ })
+
+
+ # Finish by building the epub file
+ def handle_finish(self):
+ """Create the metainfo files and finally the epub."""
+ self.get_toc()
+ self.build_mimetype(self.outdir, 'mimetype')
+ self.build_container(self.outdir, 'META-INF/container.xml')
+ self.build_content(self.outdir, 'content.opf')
+ self.build_toc(self.outdir, 'toc.ncx')
+ self.build_epub(self.outdir, self.config.epub_basename + '.epub')
+
+ 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:
+ f.write(_mimetype_template)
+ finally:
+ f.close()
+
+ def build_container(self, outdir, outname):
+ """Write the metainfo file META-INF/cointainer.xml."""
+ self.info('writing %s file...' % outname)
+ fn = path.join(outdir, outname)
+ try:
+ os.mkdir(path.dirname(fn))
+ except OSError, err:
+ if err.errno != EEXIST:
+ raise
+ f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
+ try:
+ f.write(_container_template)
+ finally:
+ f.close()
+
+ def content_metadata(self, files, spine):
+ """Create a dictionary with all metadata for the content.opf
+ file properly escaped.
+ """
+ metadata = {}
+ metadata['title'] = self.esc(self.config.epub_title)
+ metadata['author'] = self.esc(self.config.epub_author)
+ metadata['uid'] = self.esc(self.config.epub_uid)
+ metadata['lang'] = self.esc(self.config.epub_language)
+ metadata['publisher'] = self.esc(self.config.epub_publisher)
+ metadata['copyright'] = self.esc(self.config.epub_copyright)
+ metadata['scheme'] = self.esc(self.config.epub_scheme)
+ metadata['id'] = self.esc(self.config.epub_identifier)
+ metadata['files'] = files
+ metadata['spine'] = spine
+ return metadata
+
+ def build_content(self, outdir, outname):
+ """Write the metainfo file content.opf It contains bibliographic data,
+ a file list and the spine (the reading order).
+ """
+ self.info('writing %s file...' % outname)
+
+ # files
+ if not outdir.endswith(os.sep):
+ outdir += os.sep
+ olen = len(outdir)
+ projectfiles = []
+ self.files = []
+ self.ignored_files = ['.buildinfo',
+ 'mimetype', 'content.opf', 'toc.ncx', 'META-INF/container.xml',
+ self.config.epub_basename + '.epub'] + \
+ self.config.epub_exclude_files
+ for root, dirs, files in os.walk(outdir):
+ for fn in files:
+ filename = path.join(root, fn)[olen:]
+ if filename in self.ignored_files:
+ continue
+ ext = path.splitext(filename)[-1]
+ if ext not in _media_types:
+ self.warn('unknown mimetype for %s, ignoring' % filename)
+ continue
+ projectfiles.append(_file_template % {
+ 'href': self.esc(filename),
+ 'id': self.esc(self.make_id(filename)),
+ 'media_type': self.esc(_media_types[ext])
+ })
+ self.files.append(filename)
+ projectfiles = '\n'.join(projectfiles)
+
+ # spine
+ spine = []
+ for item in self.refnodes:
+ if '#' in item['refuri']:
+ continue
+ if item['refuri'] in self.ignored_files:
+ continue
+ spine.append(_spine_template % {
+ 'idref': self.esc(self.make_id(item['refuri']))
+ })
+ spine = '\n'.join(spine)
+
+ # write the project file
+ f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
+ try:
+ f.write(_content_template % \
+ self.content_metadata(projectfiles, spine))
+ finally:
+ f.close()
+
+ def new_navpoint(self, node, level, incr=True):
+ """Create a new entry in the toc from the node at given level."""
+ # XXX Modifies the node
+ if incr:
+ self.playorder += 1
+ node['indent'] = _navpoint_indent * level
+ node['navpoint'] = self.esc(_navPoint_template % self.playorder)
+ node['playorder'] = self.playorder
+ return _navpoint_template % node
+
+ def insert_subnav(self, node, subnav):
+ """Insert nested navpoints for given node.
+ The node and subnav are already rendered to text.
+ """
+ nlist = node.rsplit('\n', 1)
+ nlist.insert(-1, subnav)
+ return '\n'.join(nlist)
+
+ def build_navpoints(self, nodes):
+ """Create the toc navigation structure.
+
+ Subelements of a node are nested inside the navpoint.
+ For nested nodes the parent node is reinserted in the subnav.
+ """
+ navstack = []
+ navlist = []
+ level = 1
+ lastnode = None
+ for node in nodes:
+ file = node['refuri'].split('#')[0]
+ if file in self.ignored_files:
+ continue
+ if node['level'] > self.config.epub_tocdepth:
+ continue
+ if node['level'] == level:
+ navlist.append(self.new_navpoint(node, level))
+ elif node['level'] == level + 1:
+ navstack.append(navlist)
+ navlist = []
+ level += 1
+ if lastnode:
+ # Insert starting point in subtoc with same playOrder
+ navlist.append(self.new_navpoint(lastnode, level, False))
+ navlist.append(self.new_navpoint(node, level))
+ else:
+ while node['level'] < level:
+ subnav = '\n'.join(navlist)
+ navlist = navstack.pop()
+ navlist[-1] = self.insert_subnav(navlist[-1], subnav)
+ level -= 1
+ navlist.append(self.new_navpoint(node, level))
+ lastnode = node
+ while level != 1:
+ subnav = '\n'.join(navlist)
+ navlist = navstack.pop()
+ navlist[-1] = self.insert_subnav(navlist[-1], subnav)
+ level -= 1
+ return '\n'.join(navlist)
+
+ def toc_metadata(self, level, navpoints):
+ """Create a dictionary with all metadata for the toc.ncx
+ file properly escaped.
+ """
+ metadata = {}
+ metadata['uid'] = self.config.epub_uid
+ metadata['title'] = self.config.epub_title
+ metadata['level'] = level
+ metadata['navpoints'] = navpoints
+ return metadata
+
+ def build_toc(self, outdir, outname):
+ """Write the metainfo file toc.ncx."""
+ self.info('writing %s file...' % outname)
+
+ navpoints = self.build_navpoints(self.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:
+ f.write(_toc_template % self.toc_metadata(level, navpoints))
+ finally:
+ f.close()
+
+ def build_epub(self, outdir, outname):
+ """Write the epub file.
+
+ It is a zip file with the mimetype file stored uncompressed
+ as the first entry.
+ """
+ self.info('writing %s file...' % outname)
+ projectfiles = ['META-INF/container.xml', 'content.opf', 'toc.ncx'] \
+ + self.files
+ epub = zipfile.ZipFile(path.join(outdir, outname), 'w', \
+ zipfile.ZIP_DEFLATED)
+ epub.write(path.join(outdir, 'mimetype'), 'mimetype', \
+ zipfile.ZIP_STORED)
+ for file in projectfiles:
+ epub.write(path.join(outdir, file), file, zipfile.ZIP_DEFLATED)
+ epub.close()
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 322b9df3..2dbca037 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -11,6 +11,7 @@
import os
import sys
+import zlib
import codecs
import posixpath
import cPickle as pickle
@@ -23,20 +24,26 @@ except ImportError:
from docutils import nodes
from docutils.io import DocTreeInput, StringOutput
-from docutils.core import publish_parts
+from docutils.core import Publisher
from docutils.utils import new_document
from docutils.frontend import OptionParser
from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import package_dir, __version__
-from sphinx.util import SEP, os_path, relative_uri, ensuredir, \
- movefile, ustrftime, copy_static_entry, copyfile
+from sphinx.util import copy_static_entry
+from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
+ movefile, ustrftime, copyfile
+from sphinx.util.nodes import inline_all_toctrees
+from sphinx.util.matching import patmatch, compile_matchers
+from sphinx.util.pycompat import any
from sphinx.errors import SphinxError
+from sphinx.locale import _
from sphinx.search import js_index
from sphinx.theming import Theme
-from sphinx.builders import Builder, ENV_PICKLE_FILENAME
+from sphinx.builders import Builder
+from sphinx.application import ENV_PICKLE_FILENAME
from sphinx.highlighting import PygmentsBridge
-from sphinx.util.console import bold
+from sphinx.util.console import bold, darkgreen
from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
SmartyPantsHTMLTranslator
@@ -71,7 +78,16 @@ class StandaloneHTMLBuilder(Builder):
embedded = False # for things like HTML help or Qt help: suppresses sidebar
# This is a class attribute because it is mutated by Sphinx.add_javascript.
- script_files = ['_static/jquery.js', '_static/doctools.js']
+ script_files = ['_static/jquery.js', '_static/underscore.js',
+ '_static/doctools.js']
+ # Dito for this one.
+ css_files = []
+
+ default_sidebars = ['localtoc.html', 'relations.html',
+ 'sourcelink.html', 'searchbox.html']
+
+ # cached publisher object for snippets
+ _publisher = None
def init(self):
# a hash of all config values that, if changed, cause a full rebuild
@@ -102,9 +118,14 @@ class StandaloneHTMLBuilder(Builder):
self.script_files.append('_static/translations.js')
break
+ def get_theme_config(self):
+ return self.config.html_theme, self.config.html_theme_options
+
def init_templates(self):
Theme.init_themes(self)
- self.theme = Theme(self.config.html_theme)
+ themename, themeoptions = self.get_theme_config()
+ self.theme = Theme(themename)
+ self.theme_options = themeoptions.copy()
self.create_template_bridge()
self.templates.init(self, self.theme)
@@ -116,7 +137,8 @@ class StandaloneHTMLBuilder(Builder):
style = self.theme.get_confstr('theme', 'pygments_style', 'none')
else:
style = 'sphinx'
- self.highlighter = PygmentsBridge('html', style)
+ self.highlighter = PygmentsBridge('html', style,
+ self.config.trim_doctest_flags)
def init_translator_class(self):
if self.config.html_translator_class:
@@ -187,13 +209,24 @@ class StandaloneHTMLBuilder(Builder):
return {'fragment': ''}
doc = new_document('<partial node>')
doc.append(node)
- return publish_parts(
- doc,
- source_class=DocTreeInput,
- reader=DoctreeReader(),
- writer=HTMLWriter(self),
- settings_overrides={'output_encoding': 'unicode'}
- )
+
+ if self._publisher is None:
+ self._publisher = Publisher(
+ source_class = DocTreeInput,
+ destination_class=StringOutput)
+ self._publisher.set_components('standalone',
+ 'restructuredtext', 'pseudoxml')
+
+ pub = self._publisher
+
+ pub.reader = DoctreeReader()
+ pub.writer = HTMLWriter(self)
+ pub.process_programmatic_settings(
+ None, {'output_encoding': 'unicode'}, None)
+ pub.set_source(doc, None)
+ pub.set_destination(None, None)
+ pub.publish()
+ return pub.writer.parts
def prepare_writing(self, docnames):
from sphinx.search import IndexBuilder
@@ -204,6 +237,27 @@ class StandaloneHTMLBuilder(Builder):
self.docsettings = OptionParser(
defaults=self.env.settings,
components=(self.docwriter,)).get_default_values()
+ self.docsettings.compact_lists = bool(self.config.html_compact_lists)
+
+ # determine the additional indices to include
+ self.domain_indices = []
+ # html_domain_indices can be False/True or a list of index names
+ indices_config = self.config.html_domain_indices
+ if indices_config:
+ for domain in self.env.domains.itervalues():
+ for indexcls in domain.indices:
+ indexname = '%s-%s' % (domain.name, indexcls.name)
+ if isinstance(indices_config, list):
+ if indexname not in indices_config:
+ continue
+ # deprecated config value
+ if indexname == 'py-modindex' and \
+ not self.config.html_use_modindex:
+ continue
+ content, collapse = indexcls(domain).generate()
+ if content:
+ self.domain_indices.append(
+ (indexname, indexcls, content, collapse))
# format the "last updated on" string, only once is enough since it
# typically doesn't include the time of day
@@ -229,9 +283,11 @@ class StandaloneHTMLBuilder(Builder):
rellinks = []
if self.config.html_use_index:
rellinks.append(('genindex', _('General Index'), 'I', _('index')))
- if self.config.html_use_modindex and self.env.modules:
- rellinks.append(('modindex', _('Global Module Index'),
- 'M', _('modules')))
+ for indexname, indexcls, content, collapse in self.domain_indices:
+ # if it has a short name
+ if indexcls.shortname:
+ rellinks.append((indexname, indexcls.localname,
+ '', indexcls.shortname))
if self.config.html_style is not None:
stylename = self.config.html_style
@@ -251,11 +307,13 @@ class StandaloneHTMLBuilder(Builder):
use_opensearch = self.config.html_use_opensearch,
docstitle = self.config.html_title,
shorttitle = self.config.html_short_title,
+ show_copyright = self.config.html_show_copyright,
show_sphinx = self.config.html_show_sphinx,
has_source = self.config.html_copy_source,
show_source = self.config.html_show_sourcelink,
file_suffix = self.out_suffix,
script_files = self.script_files,
+ css_files = self.css_files,
sphinx_version = __version__,
style = stylename,
rellinks = rellinks,
@@ -267,8 +325,7 @@ class StandaloneHTMLBuilder(Builder):
if self.theme:
self.globalcontext.update(
('theme_' + key, val) for (key, val) in
- self.theme.get_options(
- self.config.html_theme_options).iteritems())
+ self.theme.get_options(self.theme_options).iteritems())
self.globalcontext.update(self.config.html_context)
def get_doc_context(self, docname, body, metatags):
@@ -359,128 +416,17 @@ class StandaloneHTMLBuilder(Builder):
def finish(self):
self.info(bold('writing additional files...'), nonl=1)
- # the global general index
+ # pages from extensions
+ for pagelist in self.app.emit('html-collect-pages'):
+ for pagename, context, template in pagelist:
+ self.handle_page(pagename, context, template)
+ # the global general index
if self.config.html_use_index:
- # the total count of lines for each index letter, used to distribute
- # the entries into two columns
- genindex = self.env.create_index(self)
- indexcounts = []
- for _, entries in genindex:
- indexcounts.append(sum(1 + len(subitems)
- for _, (_, subitems) in entries))
-
- genindexcontext = dict(
- genindexentries = genindex,
- genindexcounts = indexcounts,
- split_index = self.config.html_split_index,
- )
- self.info(' genindex', nonl=1)
-
- if self.config.html_split_index:
- self.handle_page('genindex', genindexcontext,
- 'genindex-split.html')
- self.handle_page('genindex-all', genindexcontext,
- 'genindex.html')
- for (key, entries), count in zip(genindex, indexcounts):
- ctx = {'key': key, 'entries': entries, 'count': count,
- 'genindexentries': genindex}
- self.handle_page('genindex-' + key, ctx,
- 'genindex-single.html')
- else:
- self.handle_page('genindex', genindexcontext, 'genindex.html')
-
- # the global module index
-
- if self.config.html_use_modindex and self.env.modules:
- # the sorted list of all modules, for the global module index
- modules = sorted(((mn, (self.get_relative_uri('modindex', fn) +
- '#module-' + mn, sy, pl, dep))
- for (mn, (fn, sy, pl, dep)) in
- self.env.modules.iteritems()),
- key=lambda x: x[0].lower())
- # collect all platforms
- platforms = set()
- # sort out collapsable modules
- modindexentries = []
- letters = []
- pmn = ''
- num_toplevels = 0
- num_collapsables = 0
- cg = 0 # collapse group
- fl = '' # first letter
- for mn, (fn, sy, pl, dep) in modules:
- pl = pl and pl.split(', ') or []
- platforms.update(pl)
-
- ignore = self.env.config['modindex_common_prefix']
- ignore = sorted(ignore, key=len, reverse=True)
- for i in ignore:
- if mn.startswith(i):
- mn = mn[len(i):]
- stripped = i
- break
- else:
- stripped = ''
-
- # we stripped the whole module name
- if not mn:
- continue
-
- if fl != mn[0].lower() and mn[0] != '_':
- # heading
- letter = mn[0].upper()
- if letter not in letters:
- modindexentries.append(['', False, 0, False,
- letter, '', [], False, ''])
- letters.append(letter)
- tn = mn.split('.')[0]
- if tn != mn:
- # submodule
- if pmn == tn:
- # first submodule - make parent collapsable
- modindexentries[-1][1] = True
- num_collapsables += 1
- elif not pmn.startswith(tn):
- # submodule without parent in list, add dummy entry
- cg += 1
- modindexentries.append([tn, True, cg, False, '', '',
- [], False, stripped])
- else:
- num_toplevels += 1
- cg += 1
- modindexentries.append([mn, False, cg, (tn != mn), fn, sy, pl,
- dep, stripped])
- pmn = mn
- fl = mn[0].lower()
- platforms = sorted(platforms)
-
- # apply heuristics when to collapse modindex at page load:
- # only collapse if number of toplevel modules is larger than
- # number of submodules
- collapse = len(modules) - num_toplevels < num_toplevels
-
- # As some parts of the module names may have been stripped, those
- # names have changed, thus it is necessary to sort the entries.
- if ignore:
- def sorthelper(entry):
- name = entry[0]
- if name == '':
- # heading
- name = entry[4]
- return name.lower()
-
- modindexentries.sort(key=sorthelper)
- letters.sort()
-
- modindexcontext = dict(
- modindexentries = modindexentries,
- platforms = platforms,
- letters = letters,
- collapse_modindex = collapse,
- )
- self.info(' modindex', nonl=1)
- self.handle_page('modindex', modindexcontext, 'modindex.html')
+ self.write_genindex()
+
+ # the global domain-specific indices
+ self.write_domain_indices()
# the search page
if self.name != 'htmlhelp':
@@ -499,6 +445,54 @@ class StandaloneHTMLBuilder(Builder):
self.info()
+ self.copy_image_files()
+ self.copy_download_files()
+ self.copy_static_files()
+ self.write_buildinfo()
+
+ # dump the search index
+ self.handle_finish()
+
+ def write_genindex(self):
+ # the total count of lines for each index letter, used to distribute
+ # the entries into two columns
+ genindex = self.env.create_index(self)
+ indexcounts = []
+ for _, entries in genindex:
+ indexcounts.append(sum(1 + len(subitems)
+ for _, (_, subitems) in entries))
+
+ genindexcontext = dict(
+ genindexentries = genindex,
+ genindexcounts = indexcounts,
+ split_index = self.config.html_split_index,
+ )
+ self.info(' genindex', nonl=1)
+
+ if self.config.html_split_index:
+ self.handle_page('genindex', genindexcontext,
+ 'genindex-split.html')
+ self.handle_page('genindex-all', genindexcontext,
+ 'genindex.html')
+ for (key, entries), count in zip(genindex, indexcounts):
+ ctx = {'key': key, 'entries': entries, 'count': count,
+ 'genindexentries': genindex}
+ self.handle_page('genindex-' + key, ctx,
+ 'genindex-single.html')
+ else:
+ self.handle_page('genindex', genindexcontext, 'genindex.html')
+
+ def write_domain_indices(self):
+ for indexname, indexcls, content, collapse in self.domain_indices:
+ indexcontext = dict(
+ indextitle = indexcls.localname,
+ content = content,
+ collapse_index = collapse,
+ )
+ self.info(' ' + indexname, nonl=1)
+ self.handle_page(indexname, indexcontext, 'domainindex.html')
+
+ def copy_image_files(self):
# copy image files
if self.images:
self.info(bold('copying images...'), nonl=True)
@@ -513,6 +507,7 @@ class StandaloneHTMLBuilder(Builder):
(path.join(self.srcdir, src), err))
self.info()
+ def copy_download_files(self):
# copy downloadable files
if self.env.dlfiles:
self.info(bold('copying downloadable files...'), nonl=True)
@@ -527,6 +522,7 @@ class StandaloneHTMLBuilder(Builder):
(path.join(self.srcdir, src), err))
self.info()
+ def copy_static_files(self):
# copy static files
self.info(bold('copying static files... '), nonl=True)
ensuredir(path.join(self.outdir, '_static'))
@@ -545,31 +541,42 @@ class StandaloneHTMLBuilder(Builder):
copyfile(jsfile, path.join(self.outdir, '_static',
'translations.js'))
break
- # then, copy over all user-supplied static files
+ # then, copy over theme-supplied static files
if self.theme:
- staticdirnames = [path.join(themepath, 'static')
- for themepath in self.theme.get_dirchain()[::-1]]
- else:
- staticdirnames = []
- staticdirnames += [path.join(self.confdir, spath)
- for spath in self.config.html_static_path]
- for staticdirname in staticdirnames:
- if not path.isdir(staticdirname):
- self.warn('static directory %r does not exist' % staticdirname)
+ 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, self.globalcontext)
+ # 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 +
+ ['**/' + d for d in self.config.exclude_dirnames]
+ )
+ for entry in staticentries:
+ if not path.exists(entry):
+ self.warn('html_static_path entry %r does not exist' % entry)
continue
- for filename in os.listdir(staticdirname):
- if filename.startswith('.'):
- continue
- fullname = path.join(staticdirname, filename)
- targetname = path.join(self.outdir, '_static', filename)
- copy_static_entry(fullname, targetname, self,
- self.globalcontext)
- # last, copy logo file (handled differently)
+ copy_static_entry(entry, path.join(self.outdir, '_static'), self,
+ self.globalcontext, exclude_matchers=matchers)
+ # copy logo and favicon files if not already in static path
if self.config.html_logo:
logobase = path.basename(self.config.html_logo)
- copyfile(path.join(self.confdir, self.config.html_logo),
- path.join(self.outdir, '_static', logobase))
+ logotarget = path.join(self.outdir, '_static', logobase)
+ if not path.isfile(logotarget):
+ copyfile(path.join(self.confdir, self.config.html_logo),
+ logotarget)
+ if self.config.html_favicon:
+ iconbase = path.basename(self.config.html_favicon)
+ icontarget = path.join(self.outdir, '_static', iconbase)
+ if not path.isfile(icontarget):
+ copyfile(path.join(self.confdir, self.config.html_favicon),
+ icontarget)
+ self.info('done')
+ def write_buildinfo(self):
# write build info file
fp = open(path.join(self.outdir, '.buildinfo'), 'w')
try:
@@ -581,11 +588,6 @@ class StandaloneHTMLBuilder(Builder):
finally:
fp.close()
- self.info('done')
-
- # dump the search index
- self.handle_finish()
-
def cleanup(self):
# clean up theme stuff
if self.theme:
@@ -635,13 +637,43 @@ class StandaloneHTMLBuilder(Builder):
if self.indexer is not None and title:
self.indexer.feed(pagename, title, doctree)
- def _get_local_toctree(self, docname, collapse=True):
+ def _get_local_toctree(self, docname, collapse=True, maxdepth=0):
return self.render_partial(self.env.get_toctree_for(
docname, self, collapse))['fragment']
def get_outfilename(self, pagename):
return path.join(self.outdir, os_path(pagename) + self.out_suffix)
+ def add_sidebars(self, pagename, ctx):
+ def has_wildcard(pattern):
+ return any(char in pattern for char in '*?[')
+ sidebars = None
+ matched = None
+ customsidebar = None
+ for pattern, patsidebars in self.config.html_sidebars.iteritems():
+ if patmatch(pagename, pattern):
+ if matched:
+ if has_wildcard(pattern):
+ # warn if both patterns contain wildcards
+ if has_wildcard(matched):
+ self.warn('page %s matches two patterns in '
+ 'html_sidebars: %r and %r' %
+ (pagename, matched, pattern))
+ # else the already matched pattern is more specific
+ # than the present one, because it contains no wildcard
+ continue
+ matched = pattern
+ sidebars = patsidebars
+ if sidebars is None:
+ # keep defaults
+ pass
+ elif isinstance(sidebars, basestring):
+ # 0.x compatible mode: insert custom sidebar before searchbox
+ customsidebar = sidebars
+ sidebars = None
+ ctx['sidebars'] = sidebars
+ ctx['customsidebar'] = customsidebar
+
# --------- these are overwritten by the serialization builder
def get_target_uri(self, docname, typ=None):
@@ -661,8 +693,9 @@ class StandaloneHTMLBuilder(Builder):
return uri
ctx['pathto'] = pathto
ctx['hasdoc'] = lambda name: name in self.env.all_docs
- ctx['customsidebar'] = self.config.html_sidebars.get(pagename)
+ ctx['encoding'] = encoding = self.config.html_output_encoding
ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
+ self.add_sidebars(pagename, ctx)
ctx.update(addctx)
self.app.emit('html-page-context', pagename, templatename,
@@ -681,7 +714,7 @@ class StandaloneHTMLBuilder(Builder):
# outfilename's path is in general different from self.outdir
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', 'utf-8')
+ f = codecs.open(outfilename, 'w', encoding)
try:
f.write(output)
finally:
@@ -696,6 +729,36 @@ class StandaloneHTMLBuilder(Builder):
copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self):
+ self.dump_search_index()
+ self.dump_inventory()
+
+ def dump_inventory(self):
+ self.info(bold('dumping object inventory... '), nonl=True)
+ f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
+ try:
+ f.write('# Sphinx inventory version 2\n')
+ f.write('# Project: %s\n' % self.config.project.encode('utf-8'))
+ f.write('# Version: %s\n' % self.config.version)
+ f.write('# The remainder of this file is compressed using zlib.\n')
+ compressor = zlib.compressobj(9)
+ for domainname, domain in self.env.domains.iteritems():
+ for name, dispname, type, docname, anchor, prio in \
+ domain.get_objects():
+ if anchor.endswith(name):
+ # this can shorten the inventory by as much as 25%
+ anchor = anchor[:-len(name)] + '$'
+ uri = self.get_target_uri(docname) + '#' + anchor
+ if dispname == name:
+ dispname = '-'
+ f.write(compressor.compress(
+ '%s %s:%s %s %s %s\n' % (name, domainname, type, prio,
+ uri, dispname)))
+ f.write(compressor.flush())
+ finally:
+ f.close()
+ self.info('done')
+
+ def dump_search_index(self):
self.info(bold('dumping search index... '), nonl=True)
self.indexer.prune(self.env.all_docs)
searchindexfn = path.join(self.outdir, self.searchindex_filename)
@@ -709,21 +772,6 @@ class StandaloneHTMLBuilder(Builder):
movefile(searchindexfn + '.tmp', searchindexfn)
self.info('done')
- self.info(bold('dumping object inventory... '), nonl=True)
- f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w')
- try:
- f.write('# Sphinx inventory version 1\n')
- f.write('# Project: %s\n' % self.config.project.encode('utf-8'))
- f.write('# Version: %s\n' % self.config.version)
- for modname, info in self.env.modules.iteritems():
- f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0])))
- for refname, (docname, desctype) in self.env.descrefs.iteritems():
- f.write('%s %s %s\n' % (refname, desctype,
- self.get_target_uri(docname)))
- finally:
- f.close()
- self.info('done')
-
class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
"""
@@ -751,6 +799,110 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
return outfilename
+class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
+ """
+ A StandaloneHTMLBuilder subclass that puts the whole document tree on one
+ HTML page.
+ """
+ name = 'singlehtml'
+ copysource = False
+
+ def get_outdated_docs(self):
+ return 'all documents'
+
+ def get_target_uri(self, docname, typ=None):
+ if docname in self.env.all_docs:
+ # all references are on the same page...
+ return self.config.master_doc + self.out_suffix + \
+ '#document-' + docname
+ else:
+ # chances are this is a html_additional_page
+ return docname + self.out_suffix
+
+ def get_relative_uri(self, from_, to, typ=None):
+ # ignore source
+ return self.get_target_uri(to, typ)
+
+ def fix_refuris(self, tree):
+ # fix refuris with double anchor
+ fname = self.config.master_doc + self.out_suffix
+ for refnode in tree.traverse(nodes.reference):
+ if 'refuri' not in refnode:
+ continue
+ refuri = refnode['refuri']
+ hashindex = refuri.find('#')
+ if hashindex < 0:
+ continue
+ hashindex = refuri.find('#', hashindex+1)
+ if hashindex >= 0:
+ refnode['refuri'] = fname + refuri[hashindex:]
+
+ def assemble_doctree(self):
+ master = self.config.master_doc
+ tree = self.env.get_doctree(master)
+ tree = inline_all_toctrees(self, set(), master, tree, darkgreen)
+ tree['docname'] = master
+ self.env.resolve_references(tree, master, self)
+ self.fix_refuris(tree)
+ return tree
+
+ def get_doc_context(self, docname, body, metatags):
+ # no relation links...
+ toc = self.env.get_toctree_for(self.config.master_doc, self, False)
+ self.fix_refuris(toc)
+ toc = self.render_partial(toc)['fragment']
+ return dict(
+ parents = [],
+ prev = None,
+ next = None,
+ docstitle = None,
+ title = self.config.html_title,
+ meta = None,
+ body = body,
+ metatags = metatags,
+ rellinks = [],
+ sourcename = '',
+ toc = toc,
+ display_toc = True,
+ )
+
+ def write(self, *ignored):
+ docnames = self.env.all_docs
+
+ self.info(bold('preparing documents... '), nonl=True)
+ self.prepare_writing(docnames)
+ self.info('done')
+
+ self.info(bold('assembling single document... '), nonl=True)
+ doctree = self.assemble_doctree()
+ self.info()
+ self.info(bold('writing... '), nonl=True)
+ self.write_doc(self.config.master_doc, doctree)
+ self.info('done')
+
+ def finish(self):
+ # no indices or search pages are supported
+ self.info(bold('writing additional files...'), nonl=1)
+
+ # additional pages from conf.py
+ for pagename, template in self.config.html_additional_pages.items():
+ self.info(' '+pagename, nonl=1)
+ self.handle_page(pagename, {}, template)
+
+ if self.config.html_use_opensearch:
+ self.info(' opensearch', nonl=1)
+ fn = path.join(self.outdir, '_static', 'opensearch.xml')
+ self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
+
+ self.info()
+
+ self.copy_image_files()
+ self.copy_download_files()
+ self.copy_static_files()
+ self.write_buildinfo()
+ self.dump_inventory()
+
+
class SerializingHTMLBuilder(StandaloneHTMLBuilder):
"""
An abstract builder that serializes the generated HTML.
@@ -784,9 +936,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
def handle_page(self, pagename, ctx, templatename='page.html',
outfilename=None, event_arg=None):
ctx['current_page_name'] = pagename
- sidebarfile = self.config.html_sidebars.get(pagename)
- if sidebarfile:
- ctx['customsidebar'] = sidebarfile
+ self.add_sidebars(pagename, ctx)
if not outfilename:
outfilename = path.join(self.outdir,
diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py
index bf486fc9..538f4c84 100644
--- a/sphinx/builders/htmlhelp.py
+++ b/sphinx/builders/htmlhelp.py
@@ -216,9 +216,9 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
# special books
f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
'index.html'))
- if self.config.html_use_modindex:
- f.write('<LI> ' + object_sitemap % (_('Global Module Index'),
- 'modindex.html'))
+ for indexname, indexcls, content, collapse in self.domain_indices:
+ f.write('<LI> ' + object_sitemap % (indexcls.localname,
+ '%s.html' % indexname))
# the TOC
tocdoc = self.env.get_and_resolve_doctree(
self.config.master_doc, self, prune_toctrees=False)
diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py
index 751bf28c..0481b308 100644
--- a/sphinx/builders/latex.py
+++ b/sphinx/builders/latex.py
@@ -18,9 +18,12 @@ from docutils.utils import new_document
from docutils.frontend import OptionParser
from sphinx import package_dir, addnodes
-from sphinx.util import SEP, texescape, copyfile
+from sphinx.util import texescape
+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.console import bold, darkgreen
from sphinx.writers.latex import LaTeXWriter
@@ -114,27 +117,6 @@ class LaTeXBuilder(Builder):
def assemble_doctree(self, indexfile, toctree_only, appendices):
self.docnames = set([indexfile] + appendices)
self.info(darkgreen(indexfile) + " ", nonl=1)
- def process_tree(docname, tree):
- tree = tree.deepcopy()
- for toctreenode in tree.traverse(addnodes.toctree):
- newnodes = []
- includefiles = map(str, toctreenode['includefiles'])
- for includefile in includefiles:
- try:
- self.info(darkgreen(includefile) + " ", nonl=1)
- subtree = process_tree(
- includefile, self.env.get_doctree(includefile))
- self.docnames.add(includefile)
- except Exception:
- self.warn('toctree contains ref to nonexisting '
- 'file %r' % includefile,
- self.env.doc2path(docname))
- else:
- sof = addnodes.start_of_file(docname=includefile)
- sof.children = subtree.children
- newnodes.append(sof)
- toctreenode.parent.replace(toctreenode, newnodes)
- return tree
tree = self.env.get_doctree(indexfile)
tree['docname'] = indexfile
if toctree_only:
@@ -148,7 +130,8 @@ class LaTeXBuilder(Builder):
for node in tree.traverse(addnodes.toctree):
new_sect += node
tree = new_tree
- largetree = process_tree(indexfile, tree)
+ largetree = inline_all_toctrees(self, self.docnames, indexfile, tree,
+ darkgreen)
largetree['docname'] = indexfile
for docname in appendices:
appendix = self.env.get_doctree(docname)
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 1fead717..c9bc363d 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -72,6 +72,8 @@ class CheckExternalLinksBuilder(Builder):
lineno = node.line
if uri[0:5] == 'http:' or uri[0:6] == 'https:':
+ if lineno:
+ self.info('(line %3d) ' % lineno, nonl=1)
self.info(uri, nonl=1)
if uri in self.broken:
diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py
new file mode 100644
index 00000000..756e4732
--- /dev/null
+++ b/sphinx/builders/manpage.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.builders.manpage
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Manual pages builder.
+
+ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from os import path
+
+from docutils.io import FileOutput
+from docutils.frontend import OptionParser
+
+from sphinx import addnodes
+from sphinx.errors import SphinxError
+from sphinx.builders import Builder
+from sphinx.environment import NoUri
+from sphinx.util.nodes import inline_all_toctrees
+from sphinx.util.console import bold, darkgreen
+from sphinx.writers.manpage import ManualPageWriter, has_manpage_writer
+
+
+class ManualPageBuilder(Builder):
+ """
+ Builds groff output in manual page format.
+ """
+ name = 'man'
+ format = 'man'
+ supported_image_types = []
+
+ def init(self):
+ if not has_manpage_writer:
+ raise SphinxError('The docutils manual page writer can\'t be '
+ 'found; it is only available as of docutils 0.6.')
+ if not self.config.man_pages:
+ self.warn('no "man_pages" config value found; no manual pages '
+ 'will be written')
+
+ def get_outdated_docs(self):
+ return 'all manpages' # for now
+
+ def get_target_uri(self, docname, typ=None):
+ if typ == 'token':
+ return ''
+ raise NoUri
+
+ def write(self, *ignored):
+ docwriter = ManualPageWriter(self)
+ docsettings = OptionParser(
+ defaults=self.env.settings,
+ components=(docwriter,)).get_default_values()
+
+ self.info(bold('writing... '), nonl=True)
+
+ for info in self.config.man_pages:
+ docname, name, description, authors, section = info
+ if isinstance(authors, basestring):
+ if authors:
+ authors = [authors]
+ else:
+ authors = []
+
+ targetname = '%s.%s' % (name, section)
+ self.info(darkgreen(targetname) + ' { ', nonl=True)
+ destination = FileOutput(
+ destination_path=path.join(self.outdir, targetname),
+ encoding='utf-8')
+
+ tree = self.env.get_doctree(docname)
+ docnames = set()
+ largetree = inline_all_toctrees(self, docnames, docname, tree,
+ darkgreen)
+ self.info('} ', nonl=True)
+ self.env.resolve_references(largetree, docname, self)
+ # remove pending_xref nodes
+ for pendingnode in largetree.traverse(addnodes.pending_xref):
+ pendingnode.replace_self(pendingnode.children)
+
+ largetree.settings = docsettings
+ largetree.settings.title = name
+ largetree.settings.subtitle = description
+ largetree.settings.authors = authors
+ largetree.settings.section = section
+
+ docwriter.write(largetree, destination)
+ self.info()
+
+ def finish(self):
+ pass
diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py
index 89bda0dc..cad1e020 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
+
_idpattern = re.compile(
r'(?P<title>.+) (\((?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')
@@ -126,9 +127,9 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
for node in tocdoc.traverse(istoctree):
sections.extend(self.write_toc(node))
- if self.config.html_use_modindex:
- item = section_template % {'title': _('Global Module Index'),
- 'ref': 'modindex.html'}
+ for index in self.domain_indices:
+ item = section_template % {'title': index[2],
+ 'ref': '%s-%s.html' % index[0:2]}
sections.append(' '*4*4 + item)
sections = '\n'.join(sections)
@@ -251,7 +252,7 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
shortname = shortname[:-2]
id = '%s.%s' % (id, shortname)
else:
- id = descr = None
+ id = None
if id:
item = ' '*12 + '<keyword name="%s" id="%s" ref="%s"/>' % (
diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py
index d8451371..092a1d97 100644
--- a/sphinx/builders/text.py
+++ b/sphinx/builders/text.py
@@ -14,8 +14,8 @@ from os import path
from docutils.io import StringOutput
-from sphinx.util import ensuredir, os_path
from sphinx.builders import Builder
+from sphinx.util.osutil import ensuredir, os_path
from sphinx.writers.text import TextWriter