2008-01-27 14:23:25 -06:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
sphinx.application
|
|
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Sphinx application object.
|
|
|
|
|
|
|
|
Gracefully adapted from the TextPress system by Armin.
|
|
|
|
|
|
|
|
|
|
|
|
:copyright: 2008 by Georg Brandl, Armin Ronacher.
|
|
|
|
:license: BSD.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys
|
2008-08-04 16:48:12 -05:00
|
|
|
from os import path
|
2008-01-27 14:23:25 -06:00
|
|
|
|
|
|
|
from docutils import nodes
|
|
|
|
from docutils.parsers.rst import directives, roles
|
|
|
|
|
2008-02-09 17:09:36 -06:00
|
|
|
import sphinx
|
2008-03-28 13:45:32 -05:00
|
|
|
from sphinx.roles import xfileref_role, innernodetypes
|
2008-01-27 14:23:25 -06:00
|
|
|
from sphinx.config import Config
|
|
|
|
from sphinx.builder import builtin_builders
|
2008-03-28 13:45:32 -05:00
|
|
|
from sphinx.directives import desc_directive, target_directive, additional_xref_types
|
2008-04-13 03:20:11 -05:00
|
|
|
from sphinx.environment import SphinxStandaloneReader
|
2008-02-09 17:09:36 -06:00
|
|
|
from sphinx.util.console import bold
|
2008-01-27 14:23:25 -06:00
|
|
|
|
|
|
|
|
|
|
|
class ExtensionError(Exception):
|
|
|
|
"""Raised if something's wrong with the configuration."""
|
|
|
|
|
|
|
|
def __init__(self, message, orig_exc=None):
|
|
|
|
self.message = message
|
|
|
|
self.orig_exc = orig_exc
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
if self.orig_exc:
|
|
|
|
return '%s(%r, %r)' % (self.__class__.__name__,
|
|
|
|
self.message, self.orig_exc)
|
|
|
|
return '%s(%r)' % (self.__class__.__name__, self.message)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
if self.orig_exc:
|
|
|
|
return '%s (exception: %s)' % (self.message, self.orig_exc)
|
|
|
|
return self.message
|
|
|
|
|
|
|
|
|
2008-03-09 16:31:52 -05:00
|
|
|
# List of all known core events. Maps name to arguments description.
|
2008-01-27 14:23:25 -06:00
|
|
|
events = {
|
2008-02-01 14:44:17 -06:00
|
|
|
'builder-inited': '',
|
2008-08-04 10:57:26 -05:00
|
|
|
'doctree-read': 'the doctree before being pickled',
|
|
|
|
'missing-reference': 'env, node, contnode',
|
|
|
|
'doctree-resolved': 'doctree, docname',
|
|
|
|
'env-updated': 'env',
|
2008-05-31 11:14:36 -05:00
|
|
|
'html-page-context': 'pagename, context, doctree or None',
|
2008-01-27 14:23:25 -06:00
|
|
|
}
|
|
|
|
|
2008-06-05 03:58:43 -05:00
|
|
|
CONFIG_FILENAME = 'conf.py'
|
|
|
|
|
2008-02-23 09:24:30 -06:00
|
|
|
class Sphinx(object):
|
2008-01-27 14:23:25 -06:00
|
|
|
|
2008-05-02 05:32:08 -05:00
|
|
|
def __init__(self, srcdir, confdir, outdir, doctreedir, buildername,
|
2008-01-27 14:23:25 -06:00
|
|
|
confoverrides, status, warning=sys.stderr, freshenv=False):
|
|
|
|
self.next_listener_id = 0
|
|
|
|
self._listeners = {}
|
|
|
|
self.builderclasses = builtin_builders.copy()
|
|
|
|
self.builder = None
|
|
|
|
|
2008-08-04 16:48:12 -05:00
|
|
|
self.srcdir = path.abspath(srcdir)
|
|
|
|
self.confdir = path.abspath(confdir)
|
|
|
|
self.outdir = path.abspath(outdir)
|
|
|
|
self.doctreedir = path.abspath(doctreedir)
|
2008-01-27 14:23:25 -06:00
|
|
|
|
|
|
|
self._status = status
|
|
|
|
self._warning = warning
|
2008-02-09 17:09:36 -06:00
|
|
|
self._warncount = 0
|
2008-01-27 14:23:25 -06:00
|
|
|
|
2008-03-09 16:31:52 -05:00
|
|
|
self._events = events.copy()
|
|
|
|
|
2008-01-27 14:23:25 -06:00
|
|
|
# read config
|
2008-06-05 03:58:43 -05:00
|
|
|
self.config = Config(confdir, CONFIG_FILENAME, confoverrides)
|
2008-01-27 14:23:25 -06:00
|
|
|
|
|
|
|
# load all extension modules
|
2008-05-24 13:03:56 -05:00
|
|
|
for extension in self.config.extensions:
|
2008-01-27 14:23:25 -06:00
|
|
|
self.setup_extension(extension)
|
2008-04-06 12:38:55 -05:00
|
|
|
# the config file itself can be an extension
|
2008-05-24 13:03:56 -05:00
|
|
|
if self.config.setup:
|
2008-04-06 12:38:55 -05:00
|
|
|
self.config.setup(self)
|
2008-01-27 14:23:25 -06:00
|
|
|
|
2008-06-04 15:25:27 -05:00
|
|
|
# now that we know all config values, collect them from conf.py
|
|
|
|
self.config.init_values()
|
|
|
|
|
2008-08-04 16:48:12 -05:00
|
|
|
# if the output and/or doctree dirs are within the source dir, except
|
|
|
|
# them from being searched for source files
|
|
|
|
if self.outdir.startswith(self.srcdir):
|
|
|
|
self.config.exclude_trees += [self.outdir[len(self.srcdir)+1:]]
|
|
|
|
if self.doctreedir.startswith(self.srcdir):
|
|
|
|
self.config.exclude_trees += [self.doctreedir[len(self.srcdir)+1:]]
|
|
|
|
|
2008-01-27 14:23:25 -06:00
|
|
|
if buildername is None:
|
|
|
|
print >>status, 'No builder selected, using default: html'
|
|
|
|
buildername = 'html'
|
|
|
|
if buildername not in self.builderclasses:
|
|
|
|
print >>warning, 'Builder name %s not registered' % buildername
|
|
|
|
return
|
|
|
|
|
2008-02-09 17:09:36 -06:00
|
|
|
self.info(bold('Sphinx v%s, building %s' % (sphinx.__version__, buildername)))
|
|
|
|
|
2008-01-27 14:23:25 -06:00
|
|
|
builderclass = self.builderclasses[buildername]
|
|
|
|
self.builder = builderclass(self, freshenv=freshenv)
|
|
|
|
self.emit('builder-inited')
|
|
|
|
|
|
|
|
def warn(self, message):
|
2008-02-09 17:09:36 -06:00
|
|
|
self._warncount += 1
|
2008-01-27 14:23:25 -06:00
|
|
|
self._warning.write('WARNING: %s\n' % message)
|
|
|
|
|
|
|
|
def info(self, message='', nonl=False):
|
|
|
|
if nonl:
|
|
|
|
self._status.write(message)
|
|
|
|
else:
|
|
|
|
self._status.write(message + '\n')
|
|
|
|
self._status.flush()
|
|
|
|
|
|
|
|
# general extensibility interface
|
|
|
|
|
|
|
|
def setup_extension(self, extension):
|
|
|
|
"""Import and setup a Sphinx extension module."""
|
|
|
|
try:
|
|
|
|
mod = __import__(extension, None, None, ['setup'])
|
|
|
|
except ImportError, err:
|
|
|
|
raise ExtensionError('Could not import extension %s' % extension, err)
|
|
|
|
if hasattr(mod, 'setup'):
|
|
|
|
mod.setup(self)
|
|
|
|
|
|
|
|
def import_object(self, objname, source=None):
|
|
|
|
"""Import an object from a 'module.name' string."""
|
|
|
|
try:
|
|
|
|
module, name = objname.rsplit('.', 1)
|
|
|
|
except ValueError, err:
|
|
|
|
raise ExtensionError('Invalid full object name %s' % objname +
|
|
|
|
(source and ' (needed for %s)' % source or ''), err)
|
|
|
|
try:
|
|
|
|
return getattr(__import__(module, None, None, [name]), name)
|
|
|
|
except ImportError, err:
|
|
|
|
raise ExtensionError('Could not import %s' % module +
|
|
|
|
(source and ' (needed for %s)' % source or ''), err)
|
|
|
|
except AttributeError, err:
|
|
|
|
raise ExtensionError('Could not find %s' % objname +
|
|
|
|
(source and ' (needed for %s)' % source or ''), err)
|
|
|
|
|
|
|
|
# event interface
|
|
|
|
|
|
|
|
def _validate_event(self, event):
|
|
|
|
event = intern(event)
|
2008-03-09 16:31:52 -05:00
|
|
|
if event not in self._events:
|
2008-01-27 14:23:25 -06:00
|
|
|
raise ExtensionError('Unknown event name: %s' % event)
|
|
|
|
|
|
|
|
def connect(self, event, callback):
|
|
|
|
self._validate_event(event)
|
|
|
|
listener_id = self.next_listener_id
|
|
|
|
if event not in self._listeners:
|
|
|
|
self._listeners[event] = {listener_id: callback}
|
|
|
|
else:
|
|
|
|
self._listeners[event][listener_id] = callback
|
|
|
|
self.next_listener_id += 1
|
|
|
|
return listener_id
|
|
|
|
|
|
|
|
def disconnect(self, listener_id):
|
2008-08-04 15:16:18 -05:00
|
|
|
for event in self._listeners.itervalues():
|
2008-01-27 14:23:25 -06:00
|
|
|
event.pop(listener_id, None)
|
|
|
|
|
|
|
|
def emit(self, event, *args):
|
|
|
|
result = []
|
|
|
|
if event in self._listeners:
|
|
|
|
for _, callback in self._listeners[event].iteritems():
|
|
|
|
result.append(callback(self, *args))
|
|
|
|
return result
|
|
|
|
|
2008-08-04 04:54:45 -05:00
|
|
|
def emit_firstresult(self, event, *args):
|
|
|
|
for result in self.emit(event, *args):
|
|
|
|
if result is not None:
|
|
|
|
return result
|
|
|
|
return None
|
|
|
|
|
2008-01-27 14:23:25 -06:00
|
|
|
# registering addon parts
|
|
|
|
|
|
|
|
def add_builder(self, builder):
|
|
|
|
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__))
|
|
|
|
self.builderclasses[builder.name] = builder
|
|
|
|
|
|
|
|
def add_config_value(self, name, default, rebuild_env):
|
2008-06-05 03:58:43 -05:00
|
|
|
if name in self.config.values:
|
2008-03-09 16:31:52 -05:00
|
|
|
raise ExtensionError('Config value %r already present' % name)
|
2008-06-05 03:58:43 -05:00
|
|
|
self.config.values[name] = (default, rebuild_env)
|
2008-01-27 14:23:25 -06:00
|
|
|
|
2008-03-09 16:31:52 -05:00
|
|
|
def add_event(self, name):
|
|
|
|
if name in self._events:
|
|
|
|
raise ExtensionError('Event %r already present' % name)
|
|
|
|
self._events[name] = ''
|
|
|
|
|
2008-01-27 14:23:25 -06:00
|
|
|
def add_node(self, node):
|
|
|
|
nodes._add_node_class_names([node.__name__])
|
|
|
|
|
2008-03-09 16:31:52 -05:00
|
|
|
def add_directive(self, name, func, content, arguments, **options):
|
|
|
|
func.content = content
|
|
|
|
func.arguments = arguments
|
|
|
|
func.options = options
|
|
|
|
directives.register_directive(name, func)
|
2008-01-27 14:23:25 -06:00
|
|
|
|
|
|
|
def add_role(self, name, role):
|
|
|
|
roles.register_canonical_role(name, role)
|
2008-03-09 13:18:41 -05:00
|
|
|
|
2008-03-28 13:45:32 -05:00
|
|
|
def add_description_unit(self, directivename, rolename, indextemplate='',
|
|
|
|
parse_node=None, ref_nodeclass=None):
|
|
|
|
additional_xref_types[directivename] = (rolename, indextemplate, parse_node)
|
2008-03-09 13:18:41 -05:00
|
|
|
directives.register_directive(directivename, desc_directive)
|
|
|
|
roles.register_canonical_role(rolename, xfileref_role)
|
2008-03-28 13:45:32 -05:00
|
|
|
if ref_nodeclass is not None:
|
|
|
|
innernodetypes[rolename] = ref_nodeclass
|
|
|
|
|
|
|
|
def add_crossref_type(self, directivename, rolename, indextemplate='',
|
|
|
|
ref_nodeclass=None):
|
|
|
|
additional_xref_types[directivename] = (rolename, indextemplate, None)
|
|
|
|
directives.register_directive(directivename, target_directive)
|
|
|
|
roles.register_canonical_role(rolename, xfileref_role)
|
|
|
|
if ref_nodeclass is not None:
|
|
|
|
innernodetypes[rolename] = ref_nodeclass
|
2008-04-13 03:20:11 -05:00
|
|
|
|
|
|
|
def add_transform(self, transform):
|
|
|
|
SphinxStandaloneReader.transforms.append(transform)
|
2008-04-13 13:16:55 -05:00
|
|
|
|
|
|
|
|
|
|
|
class TemplateBridge(object):
|
|
|
|
"""
|
2008-05-04 03:28:00 -05:00
|
|
|
This class defines the interface for a "template bridge", that is, a class
|
|
|
|
that renders templates given a template name and a context.
|
2008-04-13 13:16:55 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
def init(self, builder):
|
|
|
|
"""
|
|
|
|
Called by the builder to initialize the template system. *builder*
|
|
|
|
is the builder object; you'll probably want to look at the value of
|
|
|
|
``builder.config.templates_path``.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError('must be implemented in subclasses')
|
|
|
|
|
|
|
|
def newest_template_mtime(self):
|
|
|
|
"""
|
|
|
|
Called by the builder to determine if output files are outdated
|
|
|
|
because of template changes. Return the mtime of the newest template
|
|
|
|
file that was changed. The default implementation returns ``0``.
|
|
|
|
"""
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def render(self, template, context):
|
|
|
|
"""
|
|
|
|
Called by the builder to render a *template* with a specified
|
|
|
|
context (a Python dictionary).
|
|
|
|
"""
|
|
|
|
raise NotImplementedError('must be implemented in subclasses')
|