diff options
| author | Georg Brandl <georg@python.org> | 2010-05-23 16:30:04 +0200 |
|---|---|---|
| committer | Georg Brandl <georg@python.org> | 2010-05-23 16:30:04 +0200 |
| commit | c676daa50d4c09585e3e8cfadcb9bee5b688d2ba (patch) | |
| tree | c262d416cbb324710f57379f7b2c825d17357c87 /sphinx/builders | |
| parent | 38e3c0b390fafd719515d8979786727ba9bed1fb (diff) | |
| parent | 569ab21949cd3fc97f351e5e668303dd80c14680 (diff) | |
| download | sphinx-c676daa50d4c09585e3e8cfadcb9bee5b688d2ba.tar.gz | |
merge with 0.6
Diffstat (limited to 'sphinx/builders')
| -rw-r--r-- | sphinx/builders/__init__.py | 104 | ||||
| -rw-r--r-- | sphinx/builders/changes.py | 10 | ||||
| -rw-r--r-- | sphinx/builders/devhelp.py | 133 | ||||
| -rw-r--r-- | sphinx/builders/epub.py | 441 | ||||
| -rw-r--r-- | sphinx/builders/html.py | 522 | ||||
| -rw-r--r-- | sphinx/builders/htmlhelp.py | 6 | ||||
| -rw-r--r-- | sphinx/builders/latex.py | 29 | ||||
| -rw-r--r-- | sphinx/builders/linkcheck.py | 2 | ||||
| -rw-r--r-- | sphinx/builders/manpage.py | 92 | ||||
| -rw-r--r-- | sphinx/builders/qthelp.py | 9 | ||||
| -rw-r--r-- | sphinx/builders/text.py | 2 |
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('&', '&') + name = name.replace('<', '<') + name = name.replace('>', '>') + name = name.replace('"', '"') + name = name.replace('\'', ''') + 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 |
