diff options
| author | Georg Brandl <georg@python.org> | 2008-11-29 19:56:58 +0100 |
|---|---|---|
| committer | Georg Brandl <georg@python.org> | 2008-11-29 19:56:58 +0100 |
| commit | 10c994f344a0ecee8b43e94d0190b95d8d1d4aea (patch) | |
| tree | 6654473efc2533d915ed05ee0e220630ce020a51 /sphinx | |
| parent | 4ed2a4979a9d993430a62b72a8edd5536959c408 (diff) | |
| download | sphinx-10c994f344a0ecee8b43e94d0190b95d8d1d4aea.tar.gz | |
Move builders and writers into new packages.
Diffstat (limited to 'sphinx')
| -rw-r--r-- | sphinx/__init__.py | 3 | ||||
| -rw-r--r-- | sphinx/_jinja.py | 3 | ||||
| -rw-r--r-- | sphinx/application.py | 24 | ||||
| -rw-r--r-- | sphinx/builder.py | 1274 | ||||
| -rw-r--r-- | sphinx/builders/__init__.py | 328 | ||||
| -rw-r--r-- | sphinx/builders/changes.py | 137 | ||||
| -rw-r--r-- | sphinx/builders/html.py | 607 | ||||
| -rw-r--r-- | sphinx/builders/htmlhelp.py | 245 | ||||
| -rw-r--r-- | sphinx/builders/latex.py | 185 | ||||
| -rw-r--r-- | sphinx/builders/linkcheck.py (renamed from sphinx/linkcheck.py) | 6 | ||||
| -rw-r--r-- | sphinx/builders/text.py | 68 | ||||
| -rw-r--r-- | sphinx/ext/intersphinx.py | 2 | ||||
| -rw-r--r-- | sphinx/htmlhelp.py | 220 | ||||
| -rw-r--r-- | sphinx/writers/__init__.py | 10 | ||||
| -rw-r--r-- | sphinx/writers/html.py (renamed from sphinx/htmlwriter.py) | 4 | ||||
| -rw-r--r-- | sphinx/writers/latex.py (renamed from sphinx/latexwriter.py) | 4 | ||||
| -rw-r--r-- | sphinx/writers/text.py (renamed from sphinx/textwriter.py) | 4 |
17 files changed, 1625 insertions, 1499 deletions
diff --git a/sphinx/__init__.py b/sphinx/__init__.py index aa4398e0..2df41707 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -10,11 +10,14 @@ """ import sys +from os import path __revision__ = '$Revision$' __version__ = '0.5' __released__ = '0.5' +package_dir = path.abspath(path.dirname(__file__)) + def main(argv=sys.argv): if sys.version_info[:3] < (2, 4, 0): diff --git a/sphinx/_jinja.py b/sphinx/_jinja.py index d6e98b21..654e0c52 100644 --- a/sphinx/_jinja.py +++ b/sphinx/_jinja.py @@ -12,6 +12,7 @@ import codecs from os import path +from sphinx import package_dir from sphinx.util import mtimes_of_files from sphinx.application import TemplateBridge @@ -88,7 +89,7 @@ class TranslatorEnvironment(Environment): class BuiltinTemplates(TemplateBridge): def init(self, builder): self.templates = {} - base_templates_path = path.join(path.dirname(__file__), 'templates') + base_templates_path = path.join(package_dir, 'templates') ext_templates_path = [path.join(builder.confdir, dir) for dir in builder.config.templates_path] self.templates_path = [base_templates_path] + ext_templates_path diff --git a/sphinx/application.py b/sphinx/application.py index 888d7567..6c644bbe 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -22,7 +22,7 @@ from docutils.parsers.rst import directives, roles import sphinx from sphinx.roles import xfileref_role, innernodetypes from sphinx.config import Config -from sphinx.builder import builtin_builders, StandaloneHTMLBuilder +from sphinx.builders import BUILTIN_BUILDERS from sphinx.directives import desc_directive, target_directive, additional_xref_types from sphinx.environment import SphinxStandaloneReader from sphinx.util.console import bold @@ -77,7 +77,7 @@ class Sphinx(object): confoverrides, status, warning=sys.stderr, freshenv=False): self.next_listener_id = 0 self._listeners = {} - self.builderclasses = builtin_builders.copy() + self.builderclasses = BUILTIN_BUILDERS.copy() self.builder = None self.srcdir = srcdir @@ -125,6 +125,11 @@ class Sphinx(object): buildername))) builderclass = self.builderclasses[buildername] + if isinstance(builderclass, tuple): + # builtin builder + mod, cls = builderclass + builderclass = getattr( + __import__('sphinx.builders.' + mod, None, None, [cls]), cls) self.builder = builderclass(self, freshenv=freshenv) self.emit('builder-inited') @@ -220,8 +225,12 @@ class Sphinx(object): if not hasattr(builder, 'name'): raise ExtensionError('Builder class %s has no "name" attribute' % builder) if builder.name in self.builderclasses: - raise ExtensionError('Builder %r already exists (in module %s)' % ( - builder.name, self.builderclasses[builder.name].__module__)) + if isinstance(self.builderclasses[builder.name], tuple): + raise ExtensionError('Builder %r is a builtin builder' % + builder.name) + else: + raise ExtensionError('Builder %r already exists (in module %s)' % ( + builder.name, self.builderclasses[builder.name].__module__)) self.builderclasses[builder.name] = builder def add_config_value(self, name, default, rebuild_env): @@ -243,11 +252,11 @@ class Sphinx(object): raise ExtensionError('Value for key %r must be a (visit, depart) ' 'function tuple' % key) if key == 'html': - from sphinx.htmlwriter import HTMLTranslator as translator + from sphinx.writers.html import HTMLTranslator as translator elif key == 'latex': - from sphinx.latexwriter import LaTeXTranslator as translator + from sphinx.writers.latex import LaTeXTranslator as translator elif key == 'text': - from sphinx.textwriter import TextTranslator as translator + from sphinx.writers.text import TextTranslator as translator else: # ignore invalid keys for compatibility continue @@ -284,6 +293,7 @@ class Sphinx(object): SphinxStandaloneReader.transforms.append(transform) def add_javascript(self, filename): + from sphinx.builders.html import StandaloneHTMLBuilder StandaloneHTMLBuilder.script_files.append( posixpath.join('_static', filename)) diff --git a/sphinx/builder.py b/sphinx/builder.py index 159fe803..2a19b751 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -3,1268 +3,20 @@ sphinx.builder ~~~~~~~~~~~~~~ - Builder classes for different output formats. + .. warning:: - :copyright: 2007-2008 by Georg Brandl, Sebastian Wiesner, Horst Gutmann. + This module is only kept for API compatibility; new code should + import these classes directly from the sphinx.builders package. + + :copyright: 2008 by Georg Brandl. :license: BSD. """ -import os -import time -import codecs -import shutil -import gettext -import cPickle as pickle -from os import path -from cgi import escape - -from docutils import nodes -from docutils.io import StringOutput, FileOutput, DocTreeInput -from docutils.core import publish_parts -from docutils.utils import new_document -from docutils.frontend import OptionParser -from docutils.readers.doctree import Reader as DoctreeReader - -from sphinx import addnodes, locale, __version__ -from sphinx.util import ensuredir, relative_uri, SEP, os_path, texescape, ustrftime -from sphinx.htmlhelp import build_hhx -from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator -from sphinx.textwriter import TextWriter -from sphinx.latexwriter import LaTeXWriter -from sphinx.environment import BuildEnvironment, NoUri -from sphinx.highlighting import PygmentsBridge -from sphinx.util.console import bold, purple, darkgreen -from sphinx.search import js_index - -try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - json = None - -# side effect: registers roles and directives -from sphinx import roles -from sphinx import directives - -ENV_PICKLE_FILENAME = 'environment.pickle' -LAST_BUILD_FILENAME = 'last_build' -INVENTORY_FILENAME = 'objects.inv' - - -class Builder(object): - """ - Builds target formats from the reST sources. - """ - - # builder's name, for the -b command line options - name = '' - - def __init__(self, app, env=None, freshenv=False): - self.srcdir = app.srcdir - self.confdir = app.confdir - self.outdir = app.outdir - self.doctreedir = app.doctreedir - if not path.isdir(self.doctreedir): - os.makedirs(self.doctreedir) - - self.app = app - self.warn = app.warn - self.info = app.info - self.config = app.config - - self.load_i18n() - - # 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.""" - raise NotImplementedError - - def init_templates(self): - # Call this from init() if you need templates. - if self.config.template_bridge: - self.templates = self.app.import_object( - self.config.template_bridge, 'template_bridge setting')() - else: - from sphinx._jinja import BuiltinTemplates - self.templates = BuiltinTemplates() - self.templates.init(self) - - def get_target_uri(self, docname, typ=None): - """ - Return the target URI for a document name (typ can be used to qualify - the link characteristic for individual builders). - """ - raise NotImplementedError - - def get_relative_uri(self, from_, to, typ=None): - """ - Return a relative URI between two source filenames. May raise environment.NoUri - if there's no way to return a sensible URI. - """ - return relative_uri(self.get_target_uri(from_), - self.get_target_uri(to, typ)) - - def get_outdated_docs(self): - """ - Return an iterable of output files that are outdated, or a string describing - what an update build will build. - """ - raise NotImplementedError - - def status_iterator(self, iterable, summary, colorfunc=darkgreen): - l = -1 - for item in iterable: - if l == -1: - self.info(bold(summary), nonl=1) - l = 0 - self.info(colorfunc(item) + ' ', nonl=1) - yield item - if l == 0: - self.info() - - supported_image_types = [] - - def post_process_images(self, doctree): - """ - Pick the best candidate for all image URIs. - """ - for node in doctree.traverse(nodes.image): - if '?' in node['candidates']: - # don't rewrite nonlocal image URIs - continue - if '*' not in node['candidates']: - for imgtype in self.supported_image_types: - candidate = node['candidates'].get(imgtype, None) - if candidate: - break - else: - self.warn('%s:%s: no matching candidate for image URI %r' % - (node.source, getattr(node, 'lineno', ''), node['uri'])) - continue - node['uri'] = candidate - else: - candidate = node['uri'] - if candidate not in self.env.images: - # non-existing URI; let it alone - continue - self.images[candidate] = self.env.images[candidate][1] - - # 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) - locale_dirs = [path.join(path.dirname(__file__), '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 == 2: - 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') - - def build_specific(self, filenames): - """Only rebuild as much as needed for changes in the source_filenames.""" - # bring the filenames to the canonical format, that is, - # relative to the source directory and without source_suffix. - dirlen = len(self.srcdir) + 1 - to_write = [] - suffix = self.config.source_suffix - for filename in filenames: - filename = path.abspath(filename)[dirlen:] - if filename.endswith(suffix): - filename = filename[:-len(suffix)] - filename = filename.replace(os.path.sep, SEP) - to_write.append(filename) - self.build(to_write, method='specific', - summary='%d source files given on command ' - 'line' % len(to_write)) - - def build_update(self): - """Only rebuild files changed or added since last build.""" - to_build = self.get_outdated_docs() - if isinstance(to_build, str): - self.build(['__all__'], to_build) - else: - to_build = list(to_build) - self.build(to_build, - summary='targets for %d source files that are ' - 'out of date' % len(to_build)) - - def build(self, docnames, summary=None, method='update'): - if summary: - self.info(bold('building [%s]: ' % self.name), nonl=1) - self.info(summary) - - updated_docnames = [] - # while reading, collect all warnings from docutils - warnings = [] - self.env.set_warnfunc(warnings.append) - self.info(bold('updating environment: '), nonl=1) - iterator = self.env.update(self.config, self.srcdir, self.doctreedir, self.app) - # the first item in the iterator is a summary message - self.info(iterator.next()) - for docname in self.status_iterator(iterator, 'reading sources... ', purple): - updated_docnames.append(docname) - # nothing further to do, the environment has already done the reading - for warning in warnings: - if warning.strip(): - self.warn(warning) - self.env.set_warnfunc(self.warn) - - if updated_docnames: - # save the environment - self.info(bold('pickling environment... '), nonl=True) - self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME)) - self.info('done') - - # global actions - self.info(bold('checking consistency... '), nonl=True) - self.env.check_consistency() - self.info('done') - else: - if method == 'update' and not docnames: - self.info(bold('no targets are out of date.')) - return - - # another indirection to support methods which don't build files - # individually - self.write(docnames, updated_docnames, method) - - # finish (write static files etc.) - self.finish() - if self.app._warncount: - self.info(bold('build succeeded, %s warning%s.' % - (self.app._warncount, - self.app._warncount != 1 and 's' or ''))) - else: - self.info(bold('build succeeded.')) - - def write(self, build_docnames, updated_docnames, method='update'): - if build_docnames is None or build_docnames == ['__all__']: - # build_all - build_docnames = self.env.found_docs - if method == 'update': - # build updated ones as well - docnames = set(build_docnames) | set(updated_docnames) - else: - docnames = set(build_docnames) - - # add all toctree-containing files that may have changed - for docname in list(docnames): - for tocdocname in self.env.files_to_rebuild.get(docname, []): - docnames.add(tocdocname) - docnames.add(self.config.master_doc) - - self.info(bold('preparing documents... '), nonl=True) - self.prepare_writing(docnames) - self.info('done') - - # write target files - warnings = [] - self.env.set_warnfunc(warnings.append) - for docname in self.status_iterator(sorted(docnames), - 'writing output... ', darkgreen): - doctree = self.env.get_and_resolve_doctree(docname, self) - self.write_doc(docname, doctree) - for warning in warnings: - if warning.strip(): - self.warn(warning) - self.env.set_warnfunc(self.warn) - - def prepare_writing(self, docnames): - raise NotImplementedError - - def write_doc(self, docname, doctree): - raise NotImplementedError - - def finish(self): - raise NotImplementedError - - -class StandaloneHTMLBuilder(Builder): - """ - Builds standalone HTML docs. - """ - name = 'html' - copysource = True - out_suffix = '.html' - indexer_format = js_index - supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', - 'image/jpeg'] - searchindex_filename = 'searchindex.js' - add_header_links = True - add_definition_links = True - - # This is a class attribute because it is mutated by Sphinx.add_javascript. - script_files = ['_static/jquery.js', '_static/doctools.js'] - - def init(self): - """Load templates.""" - self.init_templates() - self.init_translator_class() - if self.config.html_file_suffix: - self.out_suffix = self.config.html_file_suffix - - if self.config.language is not None: - jsfile = path.join(path.dirname(__file__), 'locale', self.config.language, - 'LC_MESSAGES', 'sphinx.js') - if path.isfile(jsfile): - self.script_files.append('_static/translations.js') - - def init_translator_class(self): - if self.config.html_translator_class: - self.translator_class = self.app.import_object( - self.config.html_translator_class, 'html_translator_class setting') - elif self.config.html_use_smartypants: - self.translator_class = SmartyPantsHTMLTranslator - else: - self.translator_class = HTMLTranslator - - def render_partial(self, node): - """Utility: Render a lone doctree node.""" - 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'} - ) - - def prepare_writing(self, docnames): - from sphinx.search import IndexBuilder - - self.indexer = IndexBuilder(self.env) - self.load_indexer(docnames) - self.docwriter = HTMLWriter(self) - self.docsettings = OptionParser( - defaults=self.env.settings, - components=(self.docwriter,)).get_default_values() - - # format the "last updated on" string, only once is enough since it - # typically doesn't include the time of day - lufmt = self.config.html_last_updated_fmt - if lufmt is not None: - self.last_updated = ustrftime(lufmt or _('%b %d, %Y')) - else: - self.last_updated = None - - logo = self.config.html_logo and \ - path.basename(self.config.html_logo) or '' - - favicon = self.config.html_favicon and \ - path.basename(self.config.html_favicon) or '' - if favicon and os.path.splitext(favicon)[1] != '.ico': - self.warn('html_favicon is not an .ico file') - - if not isinstance(self.config.html_use_opensearch, basestring): - self.warn('html_use_opensearch config value must now be a string') - - self.relations = self.env.collect_relations() - - 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'))) - - self.globalcontext = dict( - project = self.config.project, - release = self.config.release, - version = self.config.version, - last_updated = self.last_updated, - copyright = self.config.copyright, - master_doc = self.config.master_doc, - style = self.config.html_style, - use_opensearch = self.config.html_use_opensearch, - docstitle = self.config.html_title, - shorttitle = self.config.html_short_title, - show_sphinx = self.config.html_show_sphinx, - file_suffix = self.out_suffix, - script_files = self.script_files, - sphinx_version = __version__, - rellinks = rellinks, - builder = self.name, - parents = [], - logo = logo, - favicon = favicon, - ) - self.globalcontext.update(self.config.html_context) - - def get_doc_context(self, docname, body, metatags): - """Collect items for the template context of a page.""" - # find out relations - prev = next = None - parents = [] - rellinks = self.globalcontext['rellinks'][:] - related = self.relations.get(docname) - titles = self.env.titles - if related and related[2]: - try: - next = {'link': self.get_relative_uri(docname, related[2]), - 'title': self.render_partial(titles[related[2]])['title']} - rellinks.append((related[2], next['title'], 'N', _('next'))) - except KeyError: - next = None - if related and related[1]: - try: - prev = {'link': self.get_relative_uri(docname, related[1]), - 'title': self.render_partial(titles[related[1]])['title']} - rellinks.append((related[1], prev['title'], 'P', _('previous'))) - except KeyError: - # the relation is (somehow) not in the TOC tree, handle that gracefully - prev = None - while related and related[0]: - try: - parents.append( - {'link': self.get_relative_uri(docname, related[0]), - 'title': self.render_partial(titles[related[0]])['title']}) - except KeyError: - pass - related = self.relations.get(related[0]) - if parents: - parents.pop() # remove link to the master file; we have a generic - # "back to index" link already - parents.reverse() - - # title rendered as HTML - title = titles.get(docname) - title = title and self.render_partial(title)['title'] or '' - # the name for the copied source - sourcename = self.config.html_copy_source and docname + '.txt' or '' - - # metadata for the document - meta = self.env.metadata.get(docname) - - return dict( - parents = parents, - prev = prev, - next = next, - title = title, - meta = meta, - body = body, - metatags = metatags, - rellinks = rellinks, - sourcename = sourcename, - toc = self.render_partial(self.env.get_toc_for(docname))['fragment'], - # only display a TOC if there's more than one item to show - display_toc = (self.env.toc_num_entries[docname] > 1), - ) - - def write_doc(self, docname, doctree): - self.post_process_images(doctree) - destination = StringOutput(encoding='utf-8') - doctree.settings = self.docsettings - - self.imgpath = relative_uri(self.get_target_uri(docname), '_images') - self.docwriter.write(doctree, destination) - self.docwriter.assemble_parts() - body = self.docwriter.parts['fragment'] - metatags = self.docwriter.clean_meta - - ctx = self.get_doc_context(docname, body, metatags) - self.index_page(docname, doctree, ctx.get('title', '')) - self.handle_page(docname, ctx, event_arg=doctree) - - def finish(self): - self.info(bold('writing additional files...'), nonl=1) - - # 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) - if fl != mn[0].lower() and mn[0] != '_': - # heading - modindexentries.append(['', False, 0, False, - mn[0].upper(), '', [], False]) - letters.append(mn[0].upper()) - 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]) - else: - num_toplevels += 1 - cg += 1 - modindexentries.append([mn, False, cg, (tn != mn), fn, sy, pl, dep]) - 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 - - modindexcontext = dict( - modindexentries = modindexentries, - platforms = platforms, - letters = letters, - collapse_modindex = collapse, - ) - self.info(' modindex', nonl=1) - self.handle_page('modindex', modindexcontext, 'modindex.html') - - # the search page - if self.name != 'htmlhelp': - self.info(' search', nonl=1) - self.handle_page('search', {}, 'search.html') - - # 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 and self.name != 'htmlhelp': - self.info(' opensearch', nonl=1) - fn = path.join(self.outdir, '_static', 'opensearch.xml') - self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn) - - self.info() - - # copy image files - if self.images: - self.info(bold('copying images...'), nonl=True) - ensuredir(path.join(self.outdir, '_images')) - for src, dest in self.images.iteritems(): - self.info(' '+src, nonl=1) - shutil.copyfile(path.join(self.srcdir, src), - path.join(self.outdir, '_images', dest)) - self.info() - - # copy static files - self.info(bold('copying static files... '), nonl=True) - ensuredir(path.join(self.outdir, '_static')) - # first, create pygments style file - f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w') - f.write(PygmentsBridge('html', self.config.pygments_style).get_stylesheet()) - f.close() - # then, copy translations JavaScript file - if self.config.language is not None: - jsfile = path.join(path.dirname(__file__), 'locale', self.config.language, - 'LC_MESSAGES', 'sphinx.js') - if path.isfile(jsfile): - shutil.copyfile(jsfile, path.join(self.outdir, '_static', - 'translations.js')) - # then, copy over all user-supplied static files - staticdirnames = [path.join(path.dirname(__file__), 'static')] + \ - [path.join(self.confdir, spath) - for spath in self.config.html_static_path] - for staticdirname in staticdirnames: - for filename in os.listdir(staticdirname): - if filename.startswith('.'): - continue - fullname = path.join(staticdirname, filename) - targetname = path.join(self.outdir, '_static', filename) - if path.isfile(fullname): - shutil.copyfile(fullname, targetname) - elif path.isdir(fullname): - if filename in self.config.exclude_dirnames: - continue - if path.exists(targetname): - shutil.rmtree(targetname) - shutil.copytree(fullname, targetname) - # last, copy logo file (handled differently) - if self.config.html_logo: - logobase = path.basename(self.config.html_logo) - shutil.copyfile(path.join(self.confdir, self.config.html_logo), - path.join(self.outdir, '_static', logobase)) - self.info('done') - - # dump the search index - self.handle_finish() - - def get_outdated_docs(self): - if self.templates: - template_mtime = self.templates.newest_template_mtime() - else: - template_mtime = 0 - for docname in self.env.found_docs: - if docname not in self.env.all_docs: - yield docname - continue - targetname = self.env.doc2path(docname, self.outdir, self.out_suffix) - try: - targetmtime = path.getmtime(targetname) - except Exception: - targetmtime = 0 - try: - srcmtime = max(path.getmtime(self.env.doc2path(docname)), - template_mtime) - if srcmtime > targetmtime: - yield docname - except EnvironmentError: - # source doesn't exist anymore - pass - - def load_indexer(self, docnames): - keep = set(self.env.all_docs) - set(docnames) - try: - f = open(path.join(self.outdir, self.searchindex_filename), 'rb') - try: - self.indexer.load(f, self.indexer_format) - finally: - f.close() - except (IOError, OSError, ValueError): - if keep: - self.warn("search index couldn't be loaded, but not all documents " - "will be built: the index will be incomplete.") - # delete all entries for files that will be rebuilt - self.indexer.prune(keep) - - def index_page(self, pagename, doctree, title): - # only index pages with title - if self.indexer is not None and title: - self.indexer.feed(pagename, title, doctree) - - # --------- these are overwritten by the serialization builder - - def get_target_uri(self, docname, typ=None): - return docname + self.out_suffix - - def handle_page(self, pagename, addctx, templatename='page.html', - outfilename=None, event_arg=None): - ctx = self.globalcontext.copy() - # current_page_name is backwards compatibility - ctx['pagename'] = ctx['current_page_name'] = pagename - - def pathto(otheruri, resource=False, - baseuri=self.get_target_uri(pagename)): - if not resource: - otheruri = self.get_target_uri(otheruri) - return relative_uri(baseuri, otheruri) - ctx['pathto'] = pathto - ctx['hasdoc'] = lambda name: name in self.env.all_docs - ctx['customsidebar'] = self.config.html_sidebars.get(pagename) - ctx.update(addctx) - - self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) - - output = self.templates.render(templatename, ctx) - if not outfilename: - outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix) - ensuredir(path.dirname(outfilename)) # normally different from self.outdir - try: - f = codecs.open(outfilename, 'w', 'utf-8') - try: - f.write(output) - finally: - f.close() - except (IOError, OSError), err: - self.warn("Error writing file %s: %s" % (outfilename, err)) - if self.copysource and ctx.get('sourcename'): - # copy the source file for the "show source" link - source_name = path.join(self.outdir, '_sources', os_path(ctx['sourcename'])) - ensuredir(path.dirname(source_name)) - shutil.copyfile(self.env.doc2path(pagename), source_name) - - def handle_finish(self): - self.info(bold('dumping search index... '), nonl=True) - self.indexer.prune(self.env.all_docs) - f = open(path.join(self.outdir, self.searchindex_filename), 'wb') - try: - self.indexer.dump(f, self.indexer_format) - finally: - f.close() - 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 SerializingHTMLBuilder(StandaloneHTMLBuilder): - """ - An abstract builder that serializes the HTML generated. - """ - #: the serializing implementation to use. Set this to a module that - #: implements a `dump`, `load`, `dumps` and `loads` functions - #: (pickle, simplejson etc.) - implementation = None - - #: the filename for the global context file - globalcontext_filename = None - - supported_image_types = ('image/svg+xml', 'image/png', 'image/gif', - 'image/jpeg') - - def init(self): - self.init_translator_class() - self.templates = None # no template bridge necessary - - def get_target_uri(self, docname, typ=None): - if docname == 'index': - return '' - if docname.endswith(SEP + 'index'): - return docname[:-5] # up to sep - return docname + SEP - - 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 - - if not outfilename: - outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix) - - self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) - - ensuredir(path.dirname(outfilename)) - f = open(outfilename, 'wb') - try: - self.implementation.dump(ctx, f, 2) - finally: - f.close() - - # if there is a source file, copy the source file for the - # "show source" link - if ctx.get('sourcename'): - source_name = path.join(self.outdir, '_sources', - os_path(ctx['sourcename'])) - ensuredir(path.dirname(source_name)) - shutil.copyfile(self.env.doc2path(pagename), source_name) - - def handle_finish(self): - # dump the global context - outfilename = path.join(self.outdir, self.globalcontext_filename) - f = open(outfilename, 'wb') - try: - self.implementation.dump(self.globalcontext, f, 2) - finally: - f.close() - - # super here to dump the search index - StandaloneHTMLBuilder.handle_finish(self) - - # copy the environment file from the doctree dir to the output dir - # as needed by the web app - shutil.copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME), - path.join(self.outdir, ENV_PICKLE_FILENAME)) - - # touch 'last build' file, used by the web application to determine - # when to reload its environment and clear the cache - open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close() - - -class PickleHTMLBuilder(SerializingHTMLBuilder): - """ - A Builder that dumps the generated HTML into pickle files. - """ - implementation = pickle - indexer_format = pickle - name = 'pickle' - out_suffix = '.fpickle' - globalcontext_filename = 'globalcontext.pickle' - searchindex_filename = 'searchindex.pickle' - - -class JSONHTMLBuilder(SerializingHTMLBuilder): - """ - A builder that dumps the generated HTML into JSON files. - """ - implementation = json - indexer_format = json - name = 'json' - out_suffix = '.fjson' - globalcontext_filename = 'globalcontext.json' - searchindex_filename = 'searchindex.json' - - def init(self): - if json is None: - from sphinx.application import SphinxError - raise SphinxError('The module simplejson (or json in Python >= 2.6) ' - 'is not available. The JSONHTMLBuilder builder ' - 'will not work.') - SerializingHTMLBuilder.init(self) - - -class HTMLHelpBuilder(StandaloneHTMLBuilder): - """ - Builder that also outputs Windows HTML help project, contents and index files. - Adapted from the original Doc/tools/prechm.py. - """ - name = 'htmlhelp' - - # don't copy the reST source - copysource = False - supported_image_types = ['image/png', 'image/gif', 'image/jpeg'] - - # don't add links - add_header_links = False - add_definition_links = False - - def init(self): - StandaloneHTMLBuilder.init(self) - # the output files for HTML help must be .html only - self.out_suffix = '.html' - - def handle_finish(self): - build_hhx(self, self.outdir, self.config.htmlhelp_basename) - - -class LaTeXBuilder(Builder): - """ - Builds LaTeX output to create PDF. - """ - name = 'latex' - supported_image_types = ['application/pdf', 'image/png', 'image/gif', - 'image/jpeg'] - - def init(self): - self.docnames = [] - self.document_data = [] - texescape.init() - - def get_outdated_docs(self): - return 'all documents' # for now - - def get_target_uri(self, docname, typ=None): - if typ == 'token': - # token references are always inside production lists and must be - # replaced by \token{} in LaTeX - return '@token' - if docname not in self.docnames: - raise NoUri - else: - return '' - - def init_document_data(self): - preliminary_document_data = map(list, self.config.latex_documents) - if not preliminary_document_data: - self.warn('No "latex_documents" config value found; no documents ' - 'will be written.') - return - # assign subdirs to titles - self.titles = [] - for entry in preliminary_document_data: - docname = entry[0] - if docname not in self.env.all_docs: - self.warn('"latex_documents" config value references unknown ' - 'document %s' % docname) - continue - self.document_data.append(entry) - if docname.endswith(SEP+'index'): - docname = docname[:-5] - self.titles.append((docname, entry[2])) - - def write(self, *ignored): - # first, assemble the "appendix" docs that are in every PDF - appendices = [] - for fname in self.config.latex_appendices: - appendices.append(self.env.get_doctree(fname)) - - docwriter = LaTeXWriter(self) - docsettings = OptionParser( - defaults=self.env.settings, - components=(docwriter,)).get_default_values() - - self.init_document_data() - - for entry in self.document_data: - docname, targetname, title, author, docclass = entry[:5] - toctree_only = False - if len(entry) > 5: - toctree_only = entry[5] - destination = FileOutput( - destination_path=path.join(self.outdir, targetname), - encoding='utf-8') - self.info("processing " + targetname + "... ", nonl=1) - doctree = self.assemble_doctree(docname, toctree_only, - appendices=(docclass == 'manual') and appendices or []) - self.post_process_images(doctree) - self.info("writing... ", nonl=1) - doctree.settings = docsettings - doctree.settings.author = author - doctree.settings.title = title - doctree.settings.docname = docname - doctree.settings.docclass = docclass - docwriter.write(doctree, destination) - self.info("done") - - 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('%s: toctree contains ref to nonexisting file %r' % - (docname, includefile)) - else: - sof = addnodes.start_of_file() - sof.children = subtree.children - newnodes.append(sof) - toctreenode.parent.replace(toctreenode, newnodes) - return tree - tree = self.env.get_doctree(indexfile) - if toctree_only: - # extract toctree nodes from the tree and put them in a fresh document - new_tree = new_document('<latex output>') - new_sect = nodes.section() - new_sect += nodes.title(u'<Set title in conf.py>', u'<Set title in conf.py>') - new_tree += new_sect - for node in tree.traverse(addnodes.toctree): - new_sect += node - tree = new_tree - largetree = process_tree(indexfile, tree) - largetree.extend(appendices) - self.info() - self.info("resolving references...") - self.env.resolve_references(largetree, indexfile, self) - # resolve :ref:s to distant tex files -- we can't add a cross-reference, - # but append the document name - for pendingnode in largetree.traverse(addnodes.pending_xref): - docname = pendingnode['refdocname'] - sectname = pendingnode['refsectname'] - newnodes = [nodes.emphasis(sectname, sectname)] - for subdir, title in self.titles: - if docname.startswith(subdir): - newnodes.append(nodes.Text(_(' (in '), _(' (in '))) - newnodes.append(nodes.emphasis(title, title)) - newnodes.append(nodes.Text(')', ')')) - break - else: - pass - pendingnode.replace_self(newnodes) - return largetree - - def finish(self): - # copy image files - if self.images: - self.info(bold('copying images...'), nonl=1) - for src, dest in self.images.iteritems(): - self.info(' '+src, nonl=1) - shutil.copyfile(path.join(self.srcdir, src), - path.join(self.outdir, dest)) - self.info() - - # the logo is handled differently - if self.config.latex_logo: - logobase = path.basename(self.config.latex_logo) - shutil.copyfile(path.join(self.confdir, self.config.latex_logo), - path.join(self.outdir, logobase)) - - self.info(bold('copying TeX support files... '), nonl=True) - staticdirname = path.join(path.dirname(__file__), 'texinputs') - for filename in os.listdir(staticdirname): - if not filename.startswith('.'): - shutil.copyfile(path.join(staticdirname, filename), - path.join(self.outdir, filename)) - self.info('done') - - -class ChangesBuilder(Builder): - """ - Write a summary with all versionadded/changed directives. - """ - name = 'changes' - - def init(self): - self.init_templates() - - def get_outdated_docs(self): - return self.outdir - - typemap = { - 'versionadded': 'added', - 'versionchanged': 'changed', - 'deprecated': 'deprecated', - } - - def write(self, *ignored): - version = self.config.version - libchanges = {} - apichanges = [] - otherchanges = {} - if version not in self.env.versionchanges: - self.info(bold('no changes in this version.')) - return - self.info(bold('writing summary file...')) - for type, docname, lineno, module, descname, content in \ - self.env.versionchanges[version]: - ttext = self.typemap[type] - context = content.replace('\n', ' ') - if descname and docname.startswith('c-api'): - if not descname: - continue - if context: - entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) - else: - entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) - apichanges.append((entry, docname, lineno)) - elif descname or module: - if not module: - module = _('Builtins') - if not descname: - descname = _('Module level') - if context: - entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) - else: - entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) - libchanges.setdefault(module, []).append((entry, docname, lineno)) - else: - if not context: - continue - entry = '<i>%s:</i> %s' % (ttext.capitalize(), context) - title = self.env.titles[docname].astext() - otherchanges.setdefault((docname, title), []).append( - (entry, docname, lineno)) - - ctx = { - 'project': self.config.project, - 'version': version, - 'docstitle': self.config.html_title, - 'shorttitle': self.config.html_short_title, - 'libchanges': sorted(libchanges.iteritems()), - 'apichanges': sorted(apichanges), - 'otherchanges': sorted(otherchanges.iteritems()), - 'show_sphinx': self.config.html_show_sphinx, - } - f = open(path.join(self.outdir, 'index.html'), 'w') - try: - f.write(self.templates.render('changes/frameset.html', ctx)) - finally: - f.close() - f = open(path.join(self.outdir, 'changes.html'), 'w') - try: - f.write(self.templates.render('changes/versionchanges.html', ctx)) - finally: - f.close() - - hltext = ['.. versionadded:: %s' % version, - '.. versionchanged:: %s' % version, - '.. deprecated:: %s' % version] - - def hl(no, line): - line = '<a name="L%s"> </a>' % no + escape(line) - for x in hltext: - if x in line: - line = '<span class="hl">%s</span>' % line - break - return line - - self.info(bold('copying source files...')) - for docname in self.env.all_docs: - f = open(self.env.doc2path(docname)) - lines = f.readlines() - targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' - ensuredir(path.dirname(targetfn)) - f = codecs.open(targetfn, 'w', 'utf8') - try: - text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines)) - ctx = {'filename': self.env.doc2path(docname, None), 'text': text} - f.write(self.templates.render('changes/rstsource.html', ctx)) - finally: - f.close() - shutil.copyfile(path.join(path.dirname(__file__), 'static', 'default.css'), - path.join(self.outdir, 'default.css')) - - def hl(self, text, version): - text = escape(text) - for directive in ['versionchanged', 'versionadded', 'deprecated']: - text = text.replace('.. %s:: %s' % (directive, version), - '<b>.. %s:: %s</b>' % (directive, version)) - return text - - def finish(self): - pass - - -class TextBuilder(Builder): - name = 'text' - out_suffix = '.txt' - - def init(self): - pass - - def get_outdated_docs(self): - for docname in self.env.found_docs: - if docname not in self.env.all_docs: - yield docname - continue - targetname = self.env.doc2path(docname, self.outdir, self.out_suffix) - try: - targetmtime = path.getmtime(targetname) - except Exception: - targetmtime = 0 - try: - srcmtime = path.getmtime(self.env.doc2path(docname)) - if srcmtime > targetmtime: - yield docname - except EnvironmentError: - # source doesn't exist anymore - pass - - def get_target_uri(self, docname, typ=None): - return '' - - def prepare_writing(self, docnames): - self.writer = TextWriter(self) - - def write_doc(self, docname, doctree): - destination = StringOutput(encoding='utf-8') - self.writer.write(doctree, destination) - outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix) - ensuredir(path.dirname(outfilename)) # normally different from self.outdir - try: - f = codecs.open(outfilename, 'w', 'utf-8') - try: - f.write(self.writer.output) - finally: - f.close() - except (IOError, OSError), err: - self.warn("Error writing file %s: %s" % (outfilename, err)) - - def finish(self): - pass - - -# compatibility alias -WebHTMLBuilder = PickleHTMLBuilder - - -from sphinx.linkcheck import CheckExternalLinksBuilder - -builtin_builders = { - 'html': StandaloneHTMLBuilder, - 'pickle': PickleHTMLBuilder, - 'json': JSONHTMLBuilder, - 'web': PickleHTMLBuilder, - 'htmlhelp': HTMLHelpBuilder, - 'latex': LaTeXBuilder, - 'text': TextBuilder, - 'changes': ChangesBuilder, - 'linkcheck': CheckExternalLinksBuilder, -} +from sphinx.builders import Builder +from sphinx.builders.text import TextBuilder +from sphinx.builders.html import StandaloneHTMLBuilder, WebHTMLBuilder, \ + PickleHTMLBuilder, JSONHTMLBuilder +from sphinx.builders.latex import LaTeXBuilder +from sphinx.builders.changes import ChangesBuilder +from sphinx.builders.htmlhelp import HTMLHelpBuilder +from sphinx.builders.linkcheck import CheckExternalLinksBuilder diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py new file mode 100644 index 00000000..09d37de2 --- /dev/null +++ b/sphinx/builders/__init__.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders + ~~~~~~~~~~~~~~~ + + Builder superclass for all builders. + + :copyright: 2007-2008 by Georg Brandl, Sebastian Wiesner, Horst Gutmann. + :license: BSD. +""" + +import os +import gettext +from os import path + +from docutils import nodes + +from sphinx import package_dir, locale +from sphinx.util import SEP, relative_uri +from sphinx.environment import BuildEnvironment +from sphinx.util.console import bold, purple, darkgreen + +# side effect: registers roles and directives +from sphinx import roles +from sphinx import directives + + +ENV_PICKLE_FILENAME = 'environment.pickle' + + +class Builder(object): + """ + Builds target formats from the reST sources. + """ + + # builder's name, for the -b command line options + name = '' + + def __init__(self, app, env=None, freshenv=False): + self.srcdir = app.srcdir + self.confdir = app.confdir + self.outdir = app.outdir + self.doctreedir = app.doctreedir + if not path.isdir(self.doctreedir): + os.makedirs(self.doctreedir) + + self.app = app + self.warn = app.warn + self.info = app.info + self.config = app.config + + self.load_i18n() + + # 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.""" + raise NotImplementedError + + def init_templates(self): + # Call this from init() if you need templates. + if self.config.template_bridge: + self.templates = self.app.import_object( + self.config.template_bridge, 'template_bridge setting')() + else: + from sphinx._jinja import BuiltinTemplates + self.templates = BuiltinTemplates() + self.templates.init(self) + + def get_target_uri(self, docname, typ=None): + """ + Return the target URI for a document name (typ can be used to qualify + the link characteristic for individual builders). + """ + raise NotImplementedError + + def get_relative_uri(self, from_, to, typ=None): + """ + Return a relative URI between two source filenames. May raise environment.NoUri + if there's no way to return a sensible URI. + """ + return relative_uri(self.get_target_uri(from_), + self.get_target_uri(to, typ)) + + def get_outdated_docs(self): + """ + Return an iterable of output files that are outdated, or a string describing + what an update build will build. + """ + raise NotImplementedError + + def status_iterator(self, iterable, summary, colorfunc=darkgreen): + l = -1 + for item in iterable: + if l == -1: + self.info(bold(summary), nonl=1) + l = 0 + self.info(colorfunc(item) + ' ', nonl=1) + yield item + if l == 0: + self.info() + + supported_image_types = [] + + def post_process_images(self, doctree): + """ + Pick the best candidate for all image URIs. + """ + for node in doctree.traverse(nodes.image): + if '?' in node['candidates']: + # don't rewrite nonlocal image URIs + continue + if '*' not in node['candidates']: + for imgtype in self.supported_image_types: + candidate = node['candidates'].get(imgtype, None) + if candidate: + break + else: + self.warn('%s:%s: no matching candidate for image URI %r' % + (node.source, getattr(node, 'lineno', ''), node['uri'])) + continue + node['uri'] = candidate + else: + candidate = node['uri'] + if candidate not in self.env.images: + # non-existing URI; let it alone + continue + self.images[candidate] = self.env.images[candidate][1] + + # 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) + locale_dirs = [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 == 2: + 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') + + def build_specific(self, filenames): + """Only rebuild as much as needed for changes in the source_filenames.""" + # bring the filenames to the canonical format, that is, + # relative to the source directory and without source_suffix. + dirlen = len(self.srcdir) + 1 + to_write = [] + suffix = self.config.source_suffix + for filename in filenames: + filename = path.abspath(filename)[dirlen:] + if filename.endswith(suffix): + filename = filename[:-len(suffix)] + filename = filename.replace(os.path.sep, SEP) + to_write.append(filename) + self.build(to_write, method='specific', + summary='%d source files given on command ' + 'line' % len(to_write)) + + def build_update(self): + """Only rebuild files changed or added since last build.""" + to_build = self.get_outdated_docs() + if isinstance(to_build, str): + self.build(['__all__'], to_build) + else: + to_build = list(to_build) + self.build(to_build, + summary='targets for %d source files that are ' + 'out of date' % len(to_build)) + + def build(self, docnames, summary=None, method='update'): + if summary: + self.info(bold('building [%s]: ' % self.name), nonl=1) + self.info(summary) + + updated_docnames = [] + # while reading, collect all warnings from docutils + warnings = [] + self.env.set_warnfunc(warnings.append) + self.info(bold('updating environment: '), nonl=1) + iterator = self.env.update(self.config, self.srcdir, self.doctreedir, self.app) + # the first item in the iterator is a summary message + self.info(iterator.next()) + for docname in self.status_iterator(iterator, 'reading sources... ', purple): + updated_docnames.append(docname) + # nothing further to do, the environment has already done the reading + for warning in warnings: + if warning.strip(): + self.warn(warning) + self.env.set_warnfunc(self.warn) + + if updated_docnames: + # save the environment + self.info(bold('pickling environment... '), nonl=True) + self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME)) + self.info('done') + + # global actions + self.info(bold('checking consistency... '), nonl=True) + self.env.check_consistency() + self.info('done') + else: + if method == 'update' and not docnames: + self.info(bold('no targets are out of date.')) + return + + # another indirection to support methods which don't build files + # individually + self.write(docnames, updated_docnames, method) + + # finish (write static files etc.) + self.finish() + if self.app._warncount: + self.info(bold('build succeeded, %s warning%s.' % + (self.app._warncount, + self.app._warncount != 1 and 's' or ''))) + else: + self.info(bold('build succeeded.')) + + def write(self, build_docnames, updated_docnames, method='update'): + if build_docnames is None or build_docnames == ['__all__']: + # build_all + build_docnames = self.env.found_docs + if method == 'update': + # build updated ones as well + docnames = set(build_docnames) | set(updated_docnames) + else: + docnames = set(build_docnames) + + # add all toctree-containing files that may have changed + for docname in list(docnames): + for tocdocname in self.env.files_to_rebuild.get(docname, []): + docnames.add(tocdocname) + docnames.add(self.config.master_doc) + + self.info(bold('preparing documents... '), nonl=True) + self.prepare_writing(docnames) + self.info('done') + + # write target files + warnings = [] + self.env.set_warnfunc(warnings.append) + for docname in self.status_iterator(sorted(docnames), + 'writing output... ', darkgreen): + doctree = self.env.get_and_resolve_doctree(docname, self) + self.write_doc(docname, doctree) + for warning in warnings: + if warning.strip(): + self.warn(warning) + self.env.set_warnfunc(self.warn) + + def prepare_writing(self, docnames): + raise NotImplementedError + + def write_doc(self, docname, doctree): + raise NotImplementedError + + def finish(self): + raise NotImplementedError + + +BUILTIN_BUILDERS = { + 'html': ('html', 'StandaloneHTMLBuilder'), + 'pickle': ('html', 'PickleHTMLBuilder'), + 'json': ('html', 'JSONHTMLBuilder'), + 'web': ('html', 'PickleHTMLBuilder'), + 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'), + 'latex': ('latex', 'LaTeXBuilder'), + 'text': ('text', 'TextBuilder'), + 'changes': ('changes', 'ChangesBuilder'), + 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'), +} diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py new file mode 100644 index 00000000..28805738 --- /dev/null +++ b/sphinx/builders/changes.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.changes + ~~~~~~~~~~~~~~~~~~~~~~~ + + Changelog builder. + + :copyright: 2008 by Georg Brandl. + :license: BSD. +""" + +import codecs +import shutil +from os import path +from cgi import escape + +from sphinx import package_dir +from sphinx.util import ensuredir, os_path +from sphinx.builders import Builder +from sphinx.util.console import bold + + +class ChangesBuilder(Builder): + """ + Write a summary with all versionadded/changed directives. + """ + name = 'changes' + + def init(self): + self.init_templates() + + def get_outdated_docs(self): + return self.outdir + + typemap = { + 'versionadded': 'added', + 'versionchanged': 'changed', + 'deprecated': 'deprecated', + } + + def write(self, *ignored): + version = self.config.version + libchanges = {} + apichanges = [] + otherchanges = {} + if version not in self.env.versionchanges: + self.info(bold('no changes in this version.')) + return + self.info(bold('writing summary file...')) + for type, docname, lineno, module, descname, content in \ + self.env.versionchanges[version]: + ttext = self.typemap[type] + context = content.replace('\n', ' ') + if descname and docname.startswith('c-api'): + if not descname: + continue + if context: + entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) + else: + entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) + apichanges.append((entry, docname, lineno)) + elif descname or module: + if not module: + module = _('Builtins') + if not descname: + descname = _('Module level') + if context: + entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) + else: + entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) + libchanges.setdefault(module, []).append((entry, docname, lineno)) + else: + if not context: + continue + entry = '<i>%s:</i> %s' % (ttext.capitalize(), context) + title = self.env.titles[docname].astext() + otherchanges.setdefault((docname, title), []).append( + (entry, docname, lineno)) + + ctx = { + 'project': self.config.project, + 'version': version, + 'docstitle': self.config.html_title, + 'shorttitle': self.config.html_short_title, + 'libchanges': sorted(libchanges.iteritems()), + 'apichanges': sorted(apichanges), + 'otherchanges': sorted(otherchanges.iteritems()), + 'show_sphinx': self.config.html_show_sphinx, + } + f = open(path.join(self.outdir, 'index.html'), 'w') + try: + f.write(self.templates.render('changes/frameset.html', ctx)) + finally: + f.close() + f = open(path.join(self.outdir, 'changes.html'), 'w') + try: + f.write(self.templates.render('changes/versionchanges.html', ctx)) + finally: + f.close() + + hltext = ['.. versionadded:: %s' % version, + '.. versionchanged:: %s' % version, + '.. deprecated:: %s' % version] + + def hl(no, line): + line = '<a name="L%s"> </a>' % no + escape(line) + for x in hltext: + if x in line: + line = '<span class="hl">%s</span>' % line + break + return line + + self.info(bold('copying source files...')) + for docname in self.env.all_docs: + f = open(self.env.doc2path(docname)) + lines = f.readlines() + targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' + ensuredir(path.dirname(targetfn)) + f = codecs.open(targetfn, 'w', 'utf8') + try: + text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines)) + ctx = {'filename': self.env.doc2path(docname, None), 'text': text} + f.write(self.templates.render('changes/rstsource.html', ctx)) + finally: + f.close() + shutil.copyfile(path.join(package_dir, 'static', 'default.css'), + path.join(self.outdir, 'default.css')) + + def hl(self, text, version): + text = escape(text) + for directive in ['versionchanged', 'versionadded', 'deprecated']: + text = text.replace('.. %s:: %s' % (directive, version), + '<b>.. %s:: %s</b>' % (directive, version)) + return text + + def finish(self): + pass diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py new file mode 100644 index 00000000..adf58be1 --- /dev/null +++ b/sphinx/builders/html.py @@ -0,0 +1,607 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.html + ~~~~~~~~~~~~~~~~~~~~ + + Several HTML builders. + + :copyright: 2007-2008 by Georg Brandl, Armin Ronacher. + :license: BSD. +""" + +import os +import codecs +import shutil +import cPickle as pickle +from os import path + +from docutils.io import DocTreeInput, StringOutput +from docutils.core import publish_parts +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, ustrftime +from sphinx.search import js_index +from sphinx.builders import Builder, ENV_PICKLE_FILENAME +from sphinx.highlighting import PygmentsBridge +from sphinx.util.console import bold +from sphinx.writers.html import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator + +try: + import json +except ImportError: + try: + import simplejson as json + except ImportError: + json = None + + +INVENTORY_FILENAME = 'objects.inv' +LAST_BUILD_FILENAME = 'last_build' + + +class StandaloneHTMLBuilder(Builder): + """ + Builds standalone HTML docs. + """ + name = 'html' + copysource = True + out_suffix = '.html' + indexer_format = js_index + supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', + 'image/jpeg'] + searchindex_filename = 'searchindex.js' + add_header_links = True + add_definition_links = True + + # This is a class attribute because it is mutated by Sphinx.add_javascript. + script_files = ['_static/jquery.js', '_static/doctools.js'] + + def init(self): + """Load templates.""" + self.init_templates() + self.init_translator_class() + if self.config.html_file_suffix: + self.out_suffix = self.config.html_file_suffix + + if self.config.language is not None: + jsfile = path.join(package_dir, 'locale', self.config.language, + 'LC_MESSAGES', 'sphinx.js') + if path.isfile(jsfile): + self.script_files.append('_static/translations.js') + + def init_translator_class(self): + if self.config.html_translator_class: + self.translator_class = self.app.import_object( + self.config.html_translator_class, 'html_translator_class setting') + elif self.config.html_use_smartypants: + self.translator_class = SmartyPantsHTMLTranslator + else: + self.translator_class = HTMLTranslator + + def render_partial(self, node): + """Utility: Render a lone doctree node.""" + 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'} + ) + + def prepare_writing(self, docnames): + from sphinx.search import IndexBuilder + + self.indexer = IndexBuilder(self.env) + self.load_indexer(docnames) + self.docwriter = HTMLWriter(self) + self.docsettings = OptionParser( + defaults=self.env.settings, + components=(self.docwriter,)).get_default_values() + + # format the "last updated on" string, only once is enough since it + # typically doesn't include the time of day + lufmt = self.config.html_last_updated_fmt + if lufmt is not None: + self.last_updated = ustrftime(lufmt or _('%b %d, %Y')) + else: + self.last_updated = None + + logo = self.config.html_logo and \ + path.basename(self.config.html_logo) or '' + + favicon = self.config.html_favicon and \ + path.basename(self.config.html_favicon) or '' + if favicon and os.path.splitext(favicon)[1] != '.ico': + self.warn('html_favicon is not an .ico file') + + if not isinstance(self.config.html_use_opensearch, basestring): + self.warn('html_use_opensearch config value must now be a string') + + self.relations = self.env.collect_relations() + + 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'))) + + self.globalcontext = dict( + project = self.config.project, + release = self.config.release, + version = self.config.version, + last_updated = self.last_updated, + copyright = self.config.copyright, + master_doc = self.config.master_doc, + style = self.config.html_style, + use_opensearch = self.config.html_use_opensearch, + docstitle = self.config.html_title, + shorttitle = self.config.html_short_title, + show_sphinx = self.config.html_show_sphinx, + file_suffix = self.out_suffix, + script_files = self.script_files, + sphinx_version = __version__, + rellinks = rellinks, + builder = self.name, + parents = [], + logo = logo, + favicon = favicon, + ) + self.globalcontext.update(self.config.html_context) + + def get_doc_context(self, docname, body, metatags): + """Collect items for the template context of a page.""" + # find out relations + prev = next = None + parents = [] + rellinks = self.globalcontext['rellinks'][:] + related = self.relations.get(docname) + titles = self.env.titles + if related and related[2]: + try: + next = {'link': self.get_relative_uri(docname, related[2]), + 'title': self.render_partial(titles[related[2]])['title']} + rellinks.append((related[2], next['title'], 'N', _('next'))) + except KeyError: + next = None + if related and related[1]: + try: + prev = {'link': self.get_relative_uri(docname, related[1]), + 'title': self.render_partial(titles[related[1]])['title']} + rellinks.append((related[1], prev['title'], 'P', _('previous'))) + except KeyError: + # the relation is (somehow) not in the TOC tree, handle that gracefully + prev = None + while related and related[0]: + try: + parents.append( + {'link': self.get_relative_uri(docname, related[0]), + 'title': self.render_partial(titles[related[0]])['title']}) + except KeyError: + pass + related = self.relations.get(related[0]) + if parents: + parents.pop() # remove link to the master file; we have a generic + # "back to index" link already + parents.reverse() + + # title rendered as HTML + title = titles.get(docname) + title = title and self.render_partial(title)['title'] or '' + # the name for the copied source + sourcename = self.config.html_copy_source and docname + '.txt' or '' + + # metadata for the document + meta = self.env.metadata.get(docname) + + return dict( + parents = parents, + prev = prev, + next = next, + title = title, + meta = meta, + body = body, + metatags = metatags, + rellinks = rellinks, + sourcename = sourcename, + toc = self.render_partial(self.env.get_toc_for(docname))['fragment'], + # only display a TOC if there's more than one item to show + display_toc = (self.env.toc_num_entries[docname] > 1), + ) + + def write_doc(self, docname, doctree): + self.post_process_images(doctree) + destination = StringOutput(encoding='utf-8') + doctree.settings = self.docsettings + + self.imgpath = relative_uri(self.get_target_uri(docname), '_images') + self.docwriter.write(doctree, destination) + self.docwriter.assemble_parts() + body = self.docwriter.parts['fragment'] + metatags = self.docwriter.clean_meta + + ctx = self.get_doc_context(docname, body, metatags) + self.index_page(docname, doctree, ctx.get('title', '')) + self.handle_page(docname, ctx, event_arg=doctree) + + def finish(self): + self.info(bold('writing additional files...'), nonl=1) + + # 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) + if fl != mn[0].lower() and mn[0] != '_': + # heading + modindexentries.append(['', False, 0, False, + mn[0].upper(), '', [], False]) + letters.append(mn[0].upper()) + 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]) + else: + num_toplevels += 1 + cg += 1 + modindexentries.append([mn, False, cg, (tn != mn), fn, sy, pl, dep]) + 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 + + modindexcontext = dict( + modindexentries = modindexentries, + platforms = platforms, + letters = letters, + collapse_modindex = collapse, + ) + self.info(' modindex', nonl=1) + self.handle_page('modindex', modindexcontext, 'modindex.html') + + # the search page + if self.name != 'htmlhelp': + self.info(' search', nonl=1) + self.handle_page('search', {}, 'search.html') + + # 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 and self.name != 'htmlhelp': + self.info(' opensearch', nonl=1) + fn = path.join(self.outdir, '_static', 'opensearch.xml') + self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn) + + self.info() + + # copy image files + if self.images: + self.info(bold('copying images...'), nonl=True) + ensuredir(path.join(self.outdir, '_images')) + for src, dest in self.images.iteritems(): + self.info(' '+src, nonl=1) + shutil.copyfile(path.join(self.srcdir, src), + path.join(self.outdir, '_images', dest)) + self.info() + + # copy static files + self.info(bold('copying static files... '), nonl=True) + ensuredir(path.join(self.outdir, '_static')) + # first, create pygments style file + f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w') + f.write(PygmentsBridge('html', self.config.pygments_style).get_stylesheet()) + f.close() + # then, copy translations JavaScript file + if self.config.language is not None: + jsfile = path.join(package_dir, 'locale', self.config.language, + 'LC_MESSAGES', 'sphinx.js') + if path.isfile(jsfile): + shutil.copyfile(jsfile, path.join(self.outdir, '_static', + 'translations.js')) + # then, copy over all user-supplied static files + staticdirnames = [path.join(package_dir, 'static')] + \ + [path.join(self.confdir, spath) + for spath in self.config.html_static_path] + for staticdirname in staticdirnames: + for filename in os.listdir(staticdirname): + if filename.startswith('.'): + continue + fullname = path.join(staticdirname, filename) + targetname = path.join(self.outdir, '_static', filename) + if path.isfile(fullname): + shutil.copyfile(fullname, targetname) + elif path.isdir(fullname): + if filename in self.config.exclude_dirnames: + continue + if path.exists(targetname): + shutil.rmtree(targetname) + shutil.copytree(fullname, targetname) + # last, copy logo file (handled differently) + if self.config.html_logo: + logobase = path.basename(self.config.html_logo) + shutil.copyfile(path.join(self.confdir, self.config.html_logo), + path.join(self.outdir, '_static', logobase)) + self.info('done') + + # dump the search index + self.handle_finish() + + def get_outdated_docs(self): + if self.templates: + template_mtime = self.templates.newest_template_mtime() + else: + template_mtime = 0 + for docname in self.env.found_docs: + if docname not in self.env.all_docs: + yield docname + continue + targetname = self.env.doc2path(docname, self.outdir, self.out_suffix) + try: + targetmtime = path.getmtime(targetname) + except Exception: + targetmtime = 0 + try: + srcmtime = max(path.getmtime(self.env.doc2path(docname)), + template_mtime) + if srcmtime > targetmtime: + yield docname + except EnvironmentError: + # source doesn't exist anymore + pass + + def load_indexer(self, docnames): + keep = set(self.env.all_docs) - set(docnames) + try: + f = open(path.join(self.outdir, self.searchindex_filename), 'rb') + try: + self.indexer.load(f, self.indexer_format) + finally: + f.close() + except (IOError, OSError, ValueError): + if keep: + self.warn("search index couldn't be loaded, but not all documents " + "will be built: the index will be incomplete.") + # delete all entries for files that will be rebuilt + self.indexer.prune(keep) + + def index_page(self, pagename, doctree, title): + # only index pages with title + if self.indexer is not None and title: + self.indexer.feed(pagename, title, doctree) + + # --------- these are overwritten by the serialization builder + + def get_target_uri(self, docname, typ=None): + return docname + self.out_suffix + + def handle_page(self, pagename, addctx, templatename='page.html', + outfilename=None, event_arg=None): + ctx = self.globalcontext.copy() + # current_page_name is backwards compatibility + ctx['pagename'] = ctx['current_page_name'] = pagename + + def pathto(otheruri, resource=False, + baseuri=self.get_target_uri(pagename)): + if not resource: + otheruri = self.get_target_uri(otheruri) + return relative_uri(baseuri, otheruri) + ctx['pathto'] = pathto + ctx['hasdoc'] = lambda name: name in self.env.all_docs + ctx['customsidebar'] = self.config.html_sidebars.get(pagename) + ctx.update(addctx) + + self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) + + output = self.templates.render(templatename, ctx) + if not outfilename: + outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix) + ensuredir(path.dirname(outfilename)) # normally different from self.outdir + try: + f = codecs.open(outfilename, 'w', 'utf-8') + try: + f.write(output) + finally: + f.close() + except (IOError, OSError), err: + self.warn("Error writing file %s: %s" % (outfilename, err)) + if self.copysource and ctx.get('sourcename'): + # copy the source file for the "show source" link + source_name = path.join(self.outdir, '_sources', os_path(ctx['sourcename'])) + ensuredir(path.dirname(source_name)) + shutil.copyfile(self.env.doc2path(pagename), source_name) + + def handle_finish(self): + self.info(bold('dumping search index... '), nonl=True) + self.indexer.prune(self.env.all_docs) + f = open(path.join(self.outdir, self.searchindex_filename), 'wb') + try: + self.indexer.dump(f, self.indexer_format) + finally: + f.close() + 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 SerializingHTMLBuilder(StandaloneHTMLBuilder): + """ + An abstract builder that serializes the HTML generated. + """ + #: the serializing implementation to use. Set this to a module that + #: implements a `dump`, `load`, `dumps` and `loads` functions + #: (pickle, simplejson etc.) + implementation = None + + #: the filename for the global context file + globalcontext_filename = None + + supported_image_types = ('image/svg+xml', 'image/png', 'image/gif', + 'image/jpeg') + + def init(self): + self.init_translator_class() + self.templates = None # no template bridge necessary + + def get_target_uri(self, docname, typ=None): + if docname == 'index': + return '' + if docname.endswith(SEP + 'index'): + return docname[:-5] # up to sep + return docname + SEP + + 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 + + if not outfilename: + outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix) + + self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) + + ensuredir(path.dirname(outfilename)) + f = open(outfilename, 'wb') + try: + self.implementation.dump(ctx, f, 2) + finally: + f.close() + + # if there is a source file, copy the source file for the + # "show source" link + if ctx.get('sourcename'): + source_name = path.join(self.outdir, '_sources', + os_path(ctx['sourcename'])) + ensuredir(path.dirname(source_name)) + shutil.copyfile(self.env.doc2path(pagename), source_name) + + def handle_finish(self): + # dump the global context + outfilename = path.join(self.outdir, self.globalcontext_filename) + f = open(outfilename, 'wb') + try: + self.implementation.dump(self.globalcontext, f, 2) + finally: + f.close() + + # super here to dump the search index + StandaloneHTMLBuilder.handle_finish(self) + + # copy the environment file from the doctree dir to the output dir + # as needed by the web app + shutil.copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME), + path.join(self.outdir, ENV_PICKLE_FILENAME)) + + # touch 'last build' file, used by the web application to determine + # when to reload its environment and clear the cache + open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close() + + +class PickleHTMLBuilder(SerializingHTMLBuilder): + """ + A Builder that dumps the generated HTML into pickle files. + """ + implementation = pickle + indexer_format = pickle + name = 'pickle' + out_suffix = '.fpickle' + globalcontext_filename = 'globalcontext.pickle' + searchindex_filename = 'searchindex.pickle' + +# compatibility alias +WebHTMLBuilder = PickleHTMLBuilder + + +class JSONHTMLBuilder(SerializingHTMLBuilder): + """ + A builder that dumps the generated HTML into JSON files. + """ + implementation = json + indexer_format = json + name = 'json' + out_suffix = '.fjson' + globalcontext_filename = 'globalcontext.json' + searchindex_filename = 'searchindex.json' + + def init(self): + if json is None: + from sphinx.application import SphinxError + raise SphinxError('The module simplejson (or json in Python >= 2.6) ' + 'is not available. The JSONHTMLBuilder builder ' + 'will not work.') + SerializingHTMLBuilder.init(self) diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py new file mode 100644 index 00000000..23900f36 --- /dev/null +++ b/sphinx/builders/htmlhelp.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.htmlhelp + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Build HTML help support files. + Parts adapted from Python's Doc/tools/prechm.py. + + :copyright: 2007-2008 by Georg Brandl. + :license: BSD. +""" + +import os +import cgi +from os import path + +from docutils import nodes + +from sphinx import addnodes +from sphinx.builders.html import StandaloneHTMLBuilder + + +# Project file (*.hhp) template. 'outname' is the file basename (like +# the pythlp in pythlp.hhp); 'version' is the doc version number (like +# the 2.2 in Python 2.2). +# The magical numbers in the long line under [WINDOWS] set most of the +# user-visible features (visible buttons, tabs, etc). +# About 0x10384e: This defines the buttons in the help viewer. The +# following defns are taken from htmlhelp.h. Not all possibilities +# actually work, and not all those that work are available from the Help +# Workshop GUI. In particular, the Zoom/Font button works and is not +# available from the GUI. The ones we're using are marked with 'x': +# +# 0x000002 Hide/Show x +# 0x000004 Back x +# 0x000008 Forward x +# 0x000010 Stop +# 0x000020 Refresh +# 0x000040 Home x +# 0x000080 Forward +# 0x000100 Back +# 0x000200 Notes +# 0x000400 Contents +# 0x000800 Locate x +# 0x001000 Options x +# 0x002000 Print x +# 0x004000 Index +# 0x008000 Search +# 0x010000 History +# 0x020000 Favorites +# 0x040000 Jump 1 +# 0x080000 Jump 2 +# 0x100000 Zoom/Font x +# 0x200000 TOC Next +# 0x400000 TOC Prev + +project_template = '''\ +[OPTIONS] +Binary TOC=Yes +Binary Index=No +Compiled file=%(outname)s.chm +Contents file=%(outname)s.hhc +Default Window=%(outname)s +Default topic=index.html +Display compile progress=No +Full text search stop list file=%(outname)s.stp +Full-text search=Yes +Index file=%(outname)s.hhk +Language=0x409 +Title=%(title)s + +[WINDOWS] +%(outname)s="%(title)s","%(outname)s.hhc","%(outname)s.hhk",\ +"index.html","index.html",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 + +[FILES] +''' + +contents_header = '''\ +<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> +<HTML> +<HEAD> +<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1"> +<!-- Sitemap 1.0 --> +</HEAD><BODY> +<OBJECT type="text/site properties"> + <param name="Window Styles" value="0x801227"> + <param name="ImageType" value="Folder"> +</OBJECT> +<UL> +''' + +contents_footer = '''\ +</UL></BODY></HTML> +''' + +object_sitemap = '''\ +<OBJECT type="text/sitemap"> + <param name="Name" value="%s"> + <param name="Local" value="%s"> +</OBJECT> +''' + +# List of words the full text search facility shouldn't index. This +# becomes file outname.stp. Note that this list must be pretty small! +# Different versions of the MS docs claim the file has a maximum size of +# 256 or 512 bytes (including \r\n at the end of each line). +# Note that "and", "or", "not" and "near" are operators in the search +# language, so no point indexing them even if we wanted to. +stopwords = """ +a and are as at +be but by +for +if in into is it +near no not +of on or +such +that the their then there these they this to +was will with +""".split() + + +class HTMLHelpBuilder(StandaloneHTMLBuilder): + """ + Builder that also outputs Windows HTML help project, contents and index files. + Adapted from the original Doc/tools/prechm.py. + """ + name = 'htmlhelp' + + # don't copy the reST source + copysource = False + supported_image_types = ['image/png', 'image/gif', 'image/jpeg'] + + # don't add links + add_header_links = False + add_definition_links = False + + def init(self): + StandaloneHTMLBuilder.init(self) + # the output files for HTML help must be .html only + self.out_suffix = '.html' + + def handle_finish(self): + self.build_hhx(self, self.outdir, self.config.htmlhelp_basename) + + def build_hhx(self, outdir, outname): + self.info('dumping stopword list...') + f = open(path.join(outdir, outname+'.stp'), 'w') + try: + for word in sorted(stopwords): + print >>f, word + finally: + f.close() + + self.info('writing project file...') + f = open(path.join(outdir, outname+'.hhp'), 'w') + try: + f.write(project_template % {'outname': outname, + 'title': self.config.html_title, + 'version': self.config.version, + 'project': self.config.project}) + if not outdir.endswith(os.sep): + outdir += os.sep + olen = len(outdir) + for root, dirs, files in os.walk(outdir): + staticdir = (root == path.join(outdir, '_static')) + for fn in files: + if (staticdir and not fn.endswith('.js')) or fn.endswith('.html'): + print >>f, path.join(root, fn)[olen:].replace(os.sep, '\\') + finally: + f.close() + + self.info('writing TOC file...') + f = open(path.join(outdir, outname+'.hhc'), 'w') + try: + f.write(contents_header) + # 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')) + # the TOC + tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self, + prune_toctrees=False) + def write_toc(node, ullevel=0): + if isinstance(node, nodes.list_item): + f.write('<LI> ') + for subnode in node: + write_toc(subnode, ullevel) + elif isinstance(node, nodes.reference): + link = node['refuri'] + title = cgi.escape(node.astext()).replace('"','"') + item = object_sitemap % (title, link) + f.write(item.encode('ascii', 'xmlcharrefreplace')) + elif isinstance(node, nodes.bullet_list): + if ullevel != 0: + f.write('<UL>\n') + for subnode in node: + write_toc(subnode, ullevel+1) + if ullevel != 0: + f.write('</UL>\n') + elif isinstance(node, addnodes.compact_paragraph): + for subnode in node: + write_toc(subnode, ullevel) + istoctree = lambda node: isinstance(node, addnodes.compact_paragraph) and \ + node.has_key('toctree') + for node in tocdoc.traverse(istoctree): + write_toc(node) + f.write(contents_footer) + finally: + f.close() + + self.info('writing index file...') + index = self.env.create_index(self) + f = open(path.join(outdir, outname+'.hhk'), 'w') + try: + f.write('<UL>\n') + def write_index(title, refs, subitems): + def write_param(name, value): + item = ' <param name="%s" value="%s">\n' % (name, value) + f.write(item.encode('ascii', 'xmlcharrefreplace')) + title = cgi.escape(title) + f.write('<LI> <OBJECT type="text/sitemap">\n') + write_param('Keyword', title) + if len(refs) == 0: + write_param('See Also', title) + elif len(refs) == 1: + write_param('Local', refs[0]) + else: + for i, ref in enumerate(refs): + write_param('Name', '[%d] %s' % (i, ref)) # XXX: better title? + write_param('Local', ref) + f.write('</OBJECT>\n') + if subitems: + f.write('<UL> ') + for subitem in subitems: + write_index(subitem[0], subitem[1], []) + f.write('</UL>') + for (key, group) in index: + for title, (refs, subitems) in group: + write_index(title, refs, subitems) + f.write('</UL>\n') + finally: + f.close() diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py new file mode 100644 index 00000000..916430db --- /dev/null +++ b/sphinx/builders/latex.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.latex + ~~~~~~~~~~~~~~~~~~~~~ + + LaTeX builder. + + :copyright: 2008 by Georg Brandl. + :license: BSD. +""" + +import os +import shutil +from os import path + +from docutils import nodes +from docutils.io import FileOutput +from docutils.utils import new_document +from docutils.frontend import OptionParser + +from sphinx import package_dir, addnodes +from sphinx.util import SEP, texescape +from sphinx.builders import Builder +from sphinx.environment import NoUri +from sphinx.util.console import bold, darkgreen +from sphinx.writers.latex import LaTeXWriter + + +class LaTeXBuilder(Builder): + """ + Builds LaTeX output to create PDF. + """ + name = 'latex' + supported_image_types = ['application/pdf', 'image/png', 'image/gif', + 'image/jpeg'] + + def init(self): + self.docnames = [] + self.document_data = [] + texescape.init() + + def get_outdated_docs(self): + return 'all documents' # for now + + def get_target_uri(self, docname, typ=None): + if typ == 'token': + # token references are always inside production lists and must be + # replaced by \token{} in LaTeX + return '@token' + if docname not in self.docnames: + raise NoUri + else: + return '' + + def init_document_data(self): + preliminary_document_data = map(list, self.config.latex_documents) + if not preliminary_document_data: + self.warn('No "latex_documents" config value found; no documents ' + 'will be written.') + return + # assign subdirs to titles + self.titles = [] + for entry in preliminary_document_data: + docname = entry[0] + if docname not in self.env.all_docs: + self.warn('"latex_documents" config value references unknown ' + 'document %s' % docname) + continue + self.document_data.append(entry) + if docname.endswith(SEP+'index'): + docname = docname[:-5] + self.titles.append((docname, entry[2])) + + def write(self, *ignored): + # first, assemble the "appendix" docs that are in every PDF + appendices = [] + for fname in self.config.latex_appendices: + appendices.append(self.env.get_doctree(fname)) + + docwriter = LaTeXWriter(self) + docsettings = OptionParser( + defaults=self.env.settings, + components=(docwriter,)).get_default_values() + + self.init_document_data() + + for entry in self.document_data: + docname, targetname, title, author, docclass = entry[:5] + toctree_only = False + if len(entry) > 5: + toctree_only = entry[5] + destination = FileOutput( + destination_path=path.join(self.outdir, targetname), + encoding='utf-8') + self.info("processing " + targetname + "... ", nonl=1) + doctree = self.assemble_doctree(docname, toctree_only, + appendices=(docclass == 'manual') and appendices or []) + self.post_process_images(doctree) + self.info("writing... ", nonl=1) + doctree.settings = docsettings + doctree.settings.author = author + doctree.settings.title = title + doctree.settings.docname = docname + doctree.settings.docclass = docclass + docwriter.write(doctree, destination) + self.info("done") + + 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('%s: toctree contains ref to nonexisting file %r' % + (docname, includefile)) + else: + sof = addnodes.start_of_file() + sof.children = subtree.children + newnodes.append(sof) + toctreenode.parent.replace(toctreenode, newnodes) + return tree + tree = self.env.get_doctree(indexfile) + if toctree_only: + # extract toctree nodes from the tree and put them in a fresh document + new_tree = new_document('<latex output>') + new_sect = nodes.section() + new_sect += nodes.title(u'<Set title in conf.py>', u'<Set title in conf.py>') + new_tree += new_sect + for node in tree.traverse(addnodes.toctree): + new_sect += node + tree = new_tree + largetree = process_tree(indexfile, tree) + largetree.extend(appendices) + self.info() + self.info("resolving references...") + self.env.resolve_references(largetree, indexfile, self) + # resolve :ref:s to distant tex files -- we can't add a cross-reference, + # but append the document name + for pendingnode in largetree.traverse(addnodes.pending_xref): + docname = pendingnode['refdocname'] + sectname = pendingnode['refsectname'] + newnodes = [nodes.emphasis(sectname, sectname)] + for subdir, title in self.titles: + if docname.startswith(subdir): + newnodes.append(nodes.Text(_(' (in '), _(' (in '))) + newnodes.append(nodes.emphasis(title, title)) + newnodes.append(nodes.Text(')', ')')) + break + else: + pass + pendingnode.replace_self(newnodes) + return largetree + + def finish(self): + # copy image files + if self.images: + self.info(bold('copying images...'), nonl=1) + for src, dest in self.images.iteritems(): + self.info(' '+src, nonl=1) + shutil.copyfile(path.join(self.srcdir, src), + path.join(self.outdir, dest)) + self.info() + + # the logo is handled differently + if self.config.latex_logo: + logobase = path.basename(self.config.latex_logo) + shutil.copyfile(path.join(self.confdir, self.config.latex_logo), + path.join(self.outdir, logobase)) + + self.info(bold('copying TeX support files... '), nonl=True) + staticdirname = path.join(package_dir, 'texinputs') + for filename in os.listdir(staticdirname): + if not filename.startswith('.'): + shutil.copyfile(path.join(staticdirname, filename), + path.join(self.outdir, filename)) + self.info('done') diff --git a/sphinx/linkcheck.py b/sphinx/builders/linkcheck.py index 37aeb7a7..9dbfc913 100644 --- a/sphinx/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - sphinx.linkcheck - ~~~~~~~~~~~~~~~~ + sphinx.builders.linkcheck + ~~~~~~~~~~~~~~~~~~~~~~~~~ The CheckExternalLinksBuilder class. @@ -15,7 +15,7 @@ from urllib2 import build_opener, HTTPError from docutils import nodes -from sphinx.builder import Builder +from sphinx.builders import Builder from sphinx.util.console import purple, red, darkgreen # create an opener that will simulate a browser user-agent diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py new file mode 100644 index 00000000..c6f232e8 --- /dev/null +++ b/sphinx/builders/text.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.text + ~~~~~~~~~~~~~~~~~~~~ + + Plain-text Sphinx builder. + + :copyright: 2008 by Georg Brandl. + :license: BSD. +""" + +import codecs +from os import path + +from docutils.io import StringOutput + +from sphinx.util import ensuredir, os_path +from sphinx.builders import Builder +from sphinx.writers.text import TextWriter + + +class TextBuilder(Builder): + name = 'text' + out_suffix = '.txt' + + def init(self): + pass + + def get_outdated_docs(self): + for docname in self.env.found_docs: + if docname not in self.env.all_docs: + yield docname + continue + targetname = self.env.doc2path(docname, self.outdir, self.out_suffix) + try: + targetmtime = path.getmtime(targetname) + except Exception: + targetmtime = 0 + try: + srcmtime = path.getmtime(self.env.doc2path(docname)) + if srcmtime > targetmtime: + yield docname + except EnvironmentError: + # source doesn't exist anymore + pass + + def get_target_uri(self, docname, typ=None): + return '' + + def prepare_writing(self, docnames): + self.writer = TextWriter(self) + + def write_doc(self, docname, doctree): + destination = StringOutput(encoding='utf-8') + self.writer.write(doctree, destination) + outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix) + ensuredir(path.dirname(outfilename)) # normally different from self.outdir + try: + f = codecs.open(outfilename, 'w', 'utf-8') + try: + f.write(self.writer.output) + finally: + f.close() + except (IOError, OSError), err: + self.warn("Error writing file %s: %s" % (outfilename, err)) + + def finish(self): + pass diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 0c034e0d..5d9607c9 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -31,7 +31,7 @@ from os import path from docutils import nodes -from sphinx.builder import INVENTORY_FILENAME +from sphinx.builders import INVENTORY_FILENAME def fetch_inventory(app, uri, inv): diff --git a/sphinx/htmlhelp.py b/sphinx/htmlhelp.py deleted file mode 100644 index 4cc68bc9..00000000 --- a/sphinx/htmlhelp.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.htmlhelp - ~~~~~~~~~~~~~~~ - - Build HTML help support files. - Adapted from the original Doc/tools/prechm.py. - - :copyright: 2007-2008 by Georg Brandl. - :license: BSD. -""" - -import os -import cgi -from os import path - -from docutils import nodes - -from sphinx import addnodes - -# Project file (*.hhp) template. 'outname' is the file basename (like -# the pythlp in pythlp.hhp); 'version' is the doc version number (like -# the 2.2 in Python 2.2). -# The magical numbers in the long line under [WINDOWS] set most of the -# user-visible features (visible buttons, tabs, etc). -# About 0x10384e: This defines the buttons in the help viewer. The -# following defns are taken from htmlhelp.h. Not all possibilities -# actually work, and not all those that work are available from the Help -# Workshop GUI. In particular, the Zoom/Font button works and is not -# available from the GUI. The ones we're using are marked with 'x': -# -# 0x000002 Hide/Show x -# 0x000004 Back x -# 0x000008 Forward x -# 0x000010 Stop -# 0x000020 Refresh -# 0x000040 Home x -# 0x000080 Forward -# 0x000100 Back -# 0x000200 Notes -# 0x000400 Contents -# 0x000800 Locate x -# 0x001000 Options x -# 0x002000 Print x -# 0x004000 Index -# 0x008000 Search -# 0x010000 History -# 0x020000 Favorites -# 0x040000 Jump 1 -# 0x080000 Jump 2 -# 0x100000 Zoom/Font x -# 0x200000 TOC Next -# 0x400000 TOC Prev - -project_template = '''\ -[OPTIONS] -Binary TOC=Yes -Binary Index=No -Compiled file=%(outname)s.chm -Contents file=%(outname)s.hhc -Default Window=%(outname)s -Default topic=index.html -Display compile progress=No -Full text search stop list file=%(outname)s.stp -Full-text search=Yes -Index file=%(outname)s.hhk -Language=0x409 -Title=%(title)s - -[WINDOWS] -%(outname)s="%(title)s","%(outname)s.hhc","%(outname)s.hhk",\ -"index.html","index.html",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 - -[FILES] -''' - -contents_header = '''\ -<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> -<HTML> -<HEAD> -<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1"> -<!-- Sitemap 1.0 --> -</HEAD><BODY> -<OBJECT type="text/site properties"> - <param name="Window Styles" value="0x801227"> - <param name="ImageType" value="Folder"> -</OBJECT> -<UL> -''' - -contents_footer = '''\ -</UL></BODY></HTML> -''' - -object_sitemap = '''\ -<OBJECT type="text/sitemap"> - <param name="Name" value="%s"> - <param name="Local" value="%s"> -</OBJECT> -''' - -# List of words the full text search facility shouldn't index. This -# becomes file outname.stp. Note that this list must be pretty small! -# Different versions of the MS docs claim the file has a maximum size of -# 256 or 512 bytes (including \r\n at the end of each line). -# Note that "and", "or", "not" and "near" are operators in the search -# language, so no point indexing them even if we wanted to. -stopwords = """ -a and are as at -be but by -for -if in into is it -near no not -of on or -such -that the their then there these they this to -was will with -""".split() - - -def build_hhx(builder, outdir, outname): - builder.info('dumping stopword list...') - f = open(path.join(outdir, outname+'.stp'), 'w') - try: - for word in sorted(stopwords): - print >>f, word - finally: - f.close() - - builder.info('writing project file...') - f = open(path.join(outdir, outname+'.hhp'), 'w') - try: - f.write(project_template % {'outname': outname, - 'title': builder.config.html_title, - 'version': builder.config.version, - 'project': builder.config.project}) - if not outdir.endswith(os.sep): - outdir += os.sep - olen = len(outdir) - for root, dirs, files in os.walk(outdir): - staticdir = (root == path.join(outdir, '_static')) - for fn in files: - if (staticdir and not fn.endswith('.js')) or fn.endswith('.html'): - print >>f, path.join(root, fn)[olen:].replace(os.sep, '\\') - finally: - f.close() - - builder.info('writing TOC file...') - f = open(path.join(outdir, outname+'.hhc'), 'w') - try: - f.write(contents_header) - # special books - f.write('<LI> ' + object_sitemap % (builder.config.html_short_title, - 'index.html')) - if builder.config.html_use_modindex: - f.write('<LI> ' + object_sitemap % (_('Global Module Index'), - 'modindex.html')) - # the TOC - tocdoc = builder.env.get_and_resolve_doctree(builder.config.master_doc, builder, - prune_toctrees=False) - def write_toc(node, ullevel=0): - if isinstance(node, nodes.list_item): - f.write('<LI> ') - for subnode in node: - write_toc(subnode, ullevel) - elif isinstance(node, nodes.reference): - link = node['refuri'] - title = cgi.escape(node.astext()).replace('"','"') - item = object_sitemap % (title, link) - f.write(item.encode('ascii', 'xmlcharrefreplace')) - elif isinstance(node, nodes.bullet_list): - if ullevel != 0: - f.write('<UL>\n') - for subnode in node: - write_toc(subnode, ullevel+1) - if ullevel != 0: - f.write('</UL>\n') - elif isinstance(node, addnodes.compact_paragraph): - for subnode in node: - write_toc(subnode, ullevel) - istoctree = lambda node: isinstance(node, addnodes.compact_paragraph) and \ - node.has_key('toctree') - for node in tocdoc.traverse(istoctree): - write_toc(node) - f.write(contents_footer) - finally: - f.close() - - builder.info('writing index file...') - index = builder.env.create_index(builder) - f = open(path.join(outdir, outname+'.hhk'), 'w') - try: - f.write('<UL>\n') - def write_index(title, refs, subitems): - def write_param(name, value): - item = ' <param name="%s" value="%s">\n' % (name, value) - f.write(item.encode('ascii', 'xmlcharrefreplace')) - title = cgi.escape(title) - f.write('<LI> <OBJECT type="text/sitemap">\n') - write_param('Keyword', title) - if len(refs) == 0: - write_param('See Also', title) - elif len(refs) == 1: - write_param('Local', refs[0]) - else: - for i, ref in enumerate(refs): - write_param('Name', '[%d] %s' % (i, ref)) # XXX: better title? - write_param('Local', ref) - f.write('</OBJECT>\n') - if subitems: - f.write('<UL> ') - for subitem in subitems: - write_index(subitem[0], subitem[1], []) - f.write('</UL>') - for (key, group) in index: - for title, (refs, subitems) in group: - write_index(title, refs, subitems) - f.write('</UL>\n') - finally: - f.close() diff --git a/sphinx/writers/__init__.py b/sphinx/writers/__init__.py new file mode 100644 index 00000000..902f04d3 --- /dev/null +++ b/sphinx/writers/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" + sphinx.writers + ~~~~~~~~~~~~~~ + + Custom docutils writers. + + :copyright: 2008 by Georg Brandl. + :license: BSD. +""" diff --git a/sphinx/htmlwriter.py b/sphinx/writers/html.py index 0505fd08..b82d7ccc 100644 --- a/sphinx/htmlwriter.py +++ b/sphinx/writers/html.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - sphinx.htmlwriter - ~~~~~~~~~~~~~~~~~ + sphinx.writers.html + ~~~~~~~~~~~~~~~~~~~ docutils writers handling Sphinx' custom nodes. diff --git a/sphinx/latexwriter.py b/sphinx/writers/latex.py index 94cb23db..bfa9c120 100644 --- a/sphinx/latexwriter.py +++ b/sphinx/writers/latex.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - sphinx.latexwriter - ~~~~~~~~~~~~~~~~~~ + sphinx.writers.latex + ~~~~~~~~~~~~~~~~~~~~ Custom docutils writer for LaTeX. diff --git a/sphinx/textwriter.py b/sphinx/writers/text.py index 4aa18039..74c637ca 100644 --- a/sphinx/textwriter.py +++ b/sphinx/writers/text.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - sphinx.textwriter - ~~~~~~~~~~~~~~~~~ + sphinx.writers.text + ~~~~~~~~~~~~~~~~~~~ Custom docutils writer for plain text. |
