Further refactorings, add extensibility API.

This commit is contained in:
Georg Brandl
2008-01-21 20:20:37 +00:00
parent b09e628b0f
commit 50da49fab4
12 changed files with 622 additions and 477 deletions
+50 -23
View File
@@ -9,15 +9,51 @@
:license: BSD.
"""
import os
import sys
import getopt
from os import path
from cStringIO import StringIO
from sphinx.config import Config, ConfigError
from sphinx.builder import builders
from sphinx.extension import EventManager
from sphinx.util.console import nocolor
__version__ = '$Revision: 5369 $'
__version__ = '$Revision: 5369 $'[11:-2]
def init_builder(buildername, srcdirname, outdirname, doctreedir,
confoverrides, status, warning=sys.stderr, freshenv=False):
# read config
config = Config(srcdirname, 'conf.py')
if confoverrides:
for key, val in confoverrides.items():
setattr(config, key, val)
# extensibility
events = EventManager()
for extension in config.extensions:
try:
mod = __import__(extension, None, None, ['setup'])
except ImportError, err:
raise ConfigError('Could not import extension %s' % module, err)
if hasattr(mod, 'setup'):
mod.setup(events, builders)
if buildername not in builders:
print >>warning, 'Builder name %s not registered' % buildername
return None
if buildername is None:
print >>status, 'No builder selected, using default: html'
buildername = 'html'
builderclass = builders[buildername]
builder = builderclass(srcdirname, outdirname, doctreedir,
status_stream=status, warning_stream=warning,
events=events, config=config, freshenv=freshenv)
events.emit('builder-created', builder)
def usage(argv, msg=None):
@@ -42,6 +78,10 @@ modi:
def main(argv):
if not sys.stdout.isatty() or sys.platform == 'win32':
# Windows' poor cmd box doesn't understand ANSI sequences
nocolor()
try:
opts, args = getopt.getopt(argv[1:], 'ab:d:D:NEqP')
srcdirname = path.abspath(args[0])
@@ -68,17 +108,14 @@ def main(argv):
if err:
return 1
builder = all_files = None
buildername = all_files = None
freshenv = use_pdb = False
status = sys.stdout
confoverrides = {}
doctreedir = path.join(outdirname, '.doctrees')
for opt, val in opts:
if opt == '-b':
if val not in builders:
usage(argv, 'Invalid builder value specified.')
return 1
builder = val
buildername = val
elif opt == '-a':
if filenames:
usage(argv, 'Cannot combine -a option and filenames.')
@@ -101,28 +138,18 @@ def main(argv):
elif opt == '-P':
use_pdb = True
if not sys.stdout.isatty() or sys.platform == 'win32':
# Windows' cmd box doesn't understand ANSI sequences
nocolor()
if builder is None:
print >>status, 'No builder selected, using default: html'
builder = 'html'
builderobj = builders[builder]
builder = init_builder(buildername, srcdirname, outdirname, doctreedir,
confoverrides, status, sys.stderr, freshenv)
if not builder:
return 1
try:
builderobj = builderobj(srcdirname, outdirname, doctreedir,
status_stream=status,
warning_stream=sys.stderr,
confoverrides=confoverrides,
freshenv=freshenv)
if all_files:
builderobj.build_all()
builder.build_all()
elif filenames:
builderobj.build_specific(filenames)
builder.build_specific(filenames)
else:
builderobj.build_update()
builder.build_update()
except:
if not use_pdb:
raise
+88 -111
View File
@@ -12,7 +12,6 @@
import os
import sys
import time
import types
import codecs
import shutil
import cPickle as pickle
@@ -31,8 +30,9 @@ from sphinx import addnodes
from sphinx.util import (get_matching_files, attrdict, status_iterator,
ensuredir, relative_uri, os_path, SEP)
from sphinx.htmlhelp import build_hhx
from sphinx.extension import DummyEventManager, import_object
from sphinx.patchlevel import get_version_info, get_sys_version_info
from sphinx.htmlwriter import HTMLWriter
from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
from sphinx.latexwriter import LaTeXWriter
from sphinx.environment import BuildEnvironment, NoUri
from sphinx.highlighting import pygments, highlight_block, get_stylesheet
@@ -45,25 +45,13 @@ from sphinx import directives
ENV_PICKLE_FILENAME = 'environment.pickle'
LAST_BUILD_FILENAME = 'last_build'
# Helper objects
class relpath_to(object):
def __init__(self, builder, filename):
self.baseuri = builder.get_target_uri(filename)
self.builder = builder
def __call__(self, otheruri, resource=False):
if not resource:
otheruri = self.builder.get_target_uri(otheruri + '.rst')
return relative_uri(self.baseuri, otheruri)
class Builder(object):
"""
Builds target formats from the reST sources.
"""
def __init__(self, srcdirname, outdirname, doctreedirname,
confoverrides=None, env=None, freshenv=False,
config, env=None, freshenv=False, events=None,
status_stream=None, warning_stream=None):
self.srcdir = srcdirname
self.outdir = outdirname
@@ -75,35 +63,11 @@ class Builder(object):
self.status_stream = status_stream or sys.stdout
self.warning_stream = warning_stream or sys.stderr
# probably set in load_env()
self.config = config
# if None, this is set in load_env()
self.env = env
self.config = {}
olddir = os.getcwd()
try:
os.chdir(srcdirname)
execfile(path.join(srcdirname, 'conf.py'), self.config)
finally:
os.chdir(olddir)
# remove potentially pickling-problematic values
del self.config['__builtins__']
for key, val in self.config.items():
if isinstance(val, types.ModuleType):
del self.config[key]
if confoverrides:
self.config.update(confoverrides)
# replace version info if '<auto>'
if self.config['version'] == '<auto>' or self.config['release'] == '<auto>':
try:
version, release = get_version_info(srcdirname)
except (IOError, OSError):
version, release = get_sys_version_info()
self.warn('Can\'t get version info from Include/patchlevel.h, '
'using version of this interpreter (%s).' % release)
if self.config['version'] == '<auto>':
self.config['version'] = version
if self.config['release'] == '<auto>':
self.config['release'] = release
self.events = events or DummyEventManager()
self.init()
@@ -124,12 +88,34 @@ class Builder(object):
"""Load necessary templates and perform initialization."""
raise NotImplementedError
def init_templates(self):
"""Call if you need Jinja templates in the builder."""
# lazily import this, maybe other builders won't need it
from sphinx._jinja import Environment, SphinxFileSystemLoader
# load templates
self.templates = {}
templates_path = [path.join(path.dirname(__file__), 'templates')]
templates_path.extend(self.config.templates_path)
self.jinja_env = Environment(loader=SphinxFileSystemLoader(templates_path),
# disable traceback, more likely that something
# in the application is broken than in the templates
friendly_traceback=False)
def get_template(self, name):
if name in self.templates:
return self.templates[name]
template = self.templates[name] = self.jinja_env.get_template(name)
return template
def get_target_uri(self, source_filename, typ=None):
"""Return the target URI for a source filename."""
raise NotImplementedError
def get_relative_uri(self, from_, to, typ=None):
"""Return a relative URI between two source filenames."""
"""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))
@@ -196,7 +182,9 @@ class Builder(object):
warnings = []
self.env.set_warnfunc(warnings.append)
self.msg('reading, updating environment:', nonl=1)
iterator = self.env.update(self.config)
iterator = self.env.update(
self.config,
hook=lambda doctree: self.events.emit('doctree-read', doctree))
self.msg(iterator.next(), nonl=1, nobold=1)
for filename in iterator:
if not updated_filenames:
@@ -274,25 +262,14 @@ class StandaloneHTMLBuilder(Builder):
def init(self):
"""Load templates."""
# lazily import this, maybe other builders won't need it
from sphinx._jinja import Environment, SphinxFileSystemLoader
# load templates
self.templates = {}
templates_path = path.join(path.dirname(__file__), 'templates')
self.jinja_env = Environment(loader=SphinxFileSystemLoader([templates_path]),
# disable traceback, more likely that something
# in the application is broken than in the templates
friendly_traceback=False)
# pre-load built-in templates
for fname in os.listdir(templates_path):
if fname.endswith('.html'):
self.templates[fname] = self.jinja_env.get_template(fname)
def get_template(self, name):
if name in self.templates:
return self.templates[name]
return self.jinja_env.get_template(name)
self.init_templates()
if self.config.html_translator_class:
self.translator_class = 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."""
@@ -317,17 +294,17 @@ class StandaloneHTMLBuilder(Builder):
# format the "last updated on" string, only once is enough since it
# typically doesn't include the time of day
lufmt = self.config.get('html_last_updated_fmt')
lufmt = self.config.html_last_updated_fmt
if lufmt:
self.last_updated = time.strftime(lufmt)
else:
self.last_updated = None
self.globalcontext = dict(
project = self.config.get('project', 'Python'),
copyright = self.config.get('copyright', ''),
release = self.config['release'],
version = self.config['version'],
project = self.config.project,
copyright = self.config.copyright,
release = self.config.release,
version = self.config.version,
last_updated = self.last_updated,
builder = self.name,
parents = [],
@@ -336,6 +313,7 @@ class StandaloneHTMLBuilder(Builder):
)
def write_file(self, filename, doctree):
pagename = filename[:-4]
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
@@ -366,7 +344,7 @@ class StandaloneHTMLBuilder(Builder):
else:
title = ''
self.globalcontext['titles'][filename] = title
sourcename = filename[:-4] + '.txt'
sourcename = pagename + '.txt'
context = dict(
title = title,
sourcename = sourcename,
@@ -379,8 +357,8 @@ class StandaloneHTMLBuilder(Builder):
next = next,
)
self.index_file(filename, doctree, title)
self.handle_page(filename[:-4], context)
self.index_page(pagename, doctree, title)
self.handle_page(pagename, context)
def finish(self):
self.msg('writing additional files...')
@@ -446,12 +424,12 @@ class StandaloneHTMLBuilder(Builder):
self.handle_page('search', {}, 'search.html')
# additional pages from conf.py
for pagename, template in self.config.get('html_additional_pages', {}).items():
for pagename, template in self.config.html_additional_pages.items():
template = path.join(self.srcdir, template)
self.handle_page(pagename, {}, template)
# the index page
indextemplate = self.config.get('html_index')
indextemplate = self.config.html_index
if indextemplate:
indextemplate = path.join(self.srcdir, indextemplate)
self.handle_page('index', {'indextemplate': indextemplate}, 'index.html')
@@ -480,7 +458,7 @@ class StandaloneHTMLBuilder(Builder):
def get_outdated_files(self):
for filename in get_matching_files(
self.srcdir, '*.rst', exclude=set(self.config.get('unused_files', ()))):
self.srcdir, '*.rst', exclude=set(self.config.unused_files)):
try:
rstname = path.join(self.outdir, os_path(filename))
targetmtime = path.getmtime(rstname[:-4] + '.html')
@@ -504,21 +482,26 @@ class StandaloneHTMLBuilder(Builder):
# delete all entries for files that will be rebuilt
self.indexer.prune([fn[:-4] for fn in set(self.env.all_files) - set(filenames)])
def index_file(self, filename, doctree, title):
def index_page(self, pagename, doctree, title):
# only index pages with title
if self.indexer is not None and title:
self.indexer.feed(self.get_target_uri(filename)[:-5], # strip '.html'
title, doctree)
self.indexer.feed(pagename, title, doctree)
def handle_page(self, pagename, context, templatename='page.html'):
def handle_page(self, pagename, addctx, templatename='page.html'):
ctx = self.globalcontext.copy()
ctx['current_page_name'] = pagename
ctx['pathto'] = relpath_to(self, self.get_target_uri(pagename+'.rst'))
def pathto(otheruri, resource=False,
baseuri=self.get_target_uri(pagename+'.rst')):
if not resource:
otheruri = self.get_target_uri(otheruri+'.rst')
return relative_uri(baseuri, otheruri)
ctx['pathto'] = pathto
ctx['hasdoc'] = lambda name: name+'.rst' in self.env.all_files
sidebarfile = self.config.get('html_sidebars', {}).get(pagename)
sidebarfile = self.config.html_sidebars.get(pagename)
if sidebarfile:
ctx['customsidebar'] = path.join(self.srcdir, sidebarfile)
ctx.update(context)
ctx.update(addctx)
output = self.get_template(templatename).render(ctx)
outfilename = path.join(self.outdir, os_path(pagename) + '.html')
@@ -531,14 +514,14 @@ class StandaloneHTMLBuilder(Builder):
f.close()
except (IOError, OSError), err:
self.warn("Error writing file %s: %s" % (outfilename, err))
if self.copysource and context.get('sourcename'):
if self.copysource and ctx.get('sourcename'):
# copy the source file for the "show source" link
shutil.copyfile(path.join(self.srcdir, os_path(pagename+'.rst')),
path.join(self.outdir, os_path(context['sourcename'])))
path.join(self.outdir, os_path(ctx['sourcename'])))
def handle_finish(self):
self.msg('dumping search index...')
self.indexer.prune([self.get_target_uri(fn)[:-5] for fn in self.env.all_files])
self.indexer.prune([fn[:-4] for fn in self.env.all_files])
f = open(path.join(self.outdir, 'searchindex.json'), 'w')
try:
self.indexer.dump(f, 'json')
@@ -558,7 +541,7 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
def get_outdated_files(self):
for filename in get_matching_files(
self.srcdir, '*.rst', exclude=set(self.config.get('unused_files', ()))):
self.srcdir, '*.rst', exclude=set(self.config.unused_files)):
try:
targetmtime = path.getmtime(
path.join(self.outdir, os_path(filename)[:-4] + '.fpickle'))
@@ -587,14 +570,14 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
# delete all entries for files that will be rebuilt
self.indexer.prune(set(self.env.all_files) - set(filenames))
def index_file(self, filename, doctree, title):
def index_page(self, pagename, doctree, title):
# only index pages with title
if self.indexer is not None and title:
self.indexer.feed(filename, title, doctree)
self.indexer.feed(pagename+'.rst', title, doctree)
def handle_page(self, pagename, context, templatename='page.html'):
context['current_page_name'] = pagename
sidebarfile = self.config.get('html_sidebars', {}).get(pagename, '')
sidebarfile = self.confightml_sidebars.get(pagename, '')
if sidebarfile:
context['customsidebar'] = path.join(self.srcdir, sidebarfile)
outfilename = path.join(self.outdir, os_path(pagename) + '.fpickle')
@@ -654,7 +637,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
copysource = False
def handle_finish(self):
build_hhx(self, self.outdir, self.config.get('htmlhelp_basename', 'pydoc'))
build_hhx(self, self.outdir, self.config.htmlhelp_basename)
class LaTeXBuilder(Builder):
@@ -665,13 +648,12 @@ class LaTeXBuilder(Builder):
def init(self):
self.filenames = []
self.document_data = map(list, self.config.get('latex_documents', ()))
self.document_data = map(list, self.config.latex_documents)
# assign subdirs to titles
self.titles = []
for entry in self.document_data:
# replace version with real version
entry[0] = entry[0].replace('<auto>', self.config['version'])
sourcename = entry[0]
if sourcename.endswith('/index.rst'):
sourcename = sourcename[:-9]
@@ -693,7 +675,7 @@ class LaTeXBuilder(Builder):
def write(self, *ignored):
# first, assemble the "appendix" docs that are in every PDF
appendices = []
for fname in self.config.get('latex_appendices', []):
for fname in self.config.latex_appendices:
appendices.append(self.env.get_doctree(fname))
docwriter = LaTeXWriter(self)
@@ -780,15 +762,10 @@ class ChangesBuilder(Builder):
name = 'changes'
def init(self):
from sphinx._jinja import Environment, FileSystemLoader
templates_path = path.join(path.dirname(__file__), 'templates')
jinja_env = Environment(loader=SphinxFileSystemLoader([templates_path]),
# disable traceback, more likely that something in the
# application is broken than in the templates
friendly_traceback=False)
self.ftemplate = jinja_env.get_template('changes/frameset.html')
self.vtemplate = jinja_env.get_template('changes/versionchanges.html')
self.stemplate = jinja_env.get_template('changes/rstsource.html')
self.init_templates()
self.ftemplate = self.get_template('changes/frameset.html')
self.vtemplate = self.get_template('changes/versionchanges.html')
self.stemplate = self.get_template('changes/rstsource.html')
def get_outdated_files(self):
return self.outdir
@@ -800,13 +777,13 @@ class ChangesBuilder(Builder):
}
def write(self, *ignored):
ver = self.config['version']
version = self.config.version
libchanges = {}
apichanges = []
otherchanges = {}
self.msg('writing summary file...')
for type, filename, lineno, module, descname, content in \
self.env.versionchanges[ver]:
self.env.versionchanges[version]:
ttext = self.typemap[type]
context = content.replace('\n', ' ')
if descname and filename.startswith('c-api'):
@@ -836,8 +813,8 @@ class ChangesBuilder(Builder):
(entry, filename, lineno))
ctx = {
'project': self.config.get('project', 'Python'),
'version': ver,
'project': self.config.project,
'version': version,
'libchanges': sorted(libchanges.iteritems()),
'apichanges': sorted(apichanges),
'otherchanges': sorted(otherchanges.iteritems()),
@@ -853,9 +830,9 @@ class ChangesBuilder(Builder):
finally:
f.close()
hltext = ['.. versionadded:: %s' % ver,
'.. versionchanged:: %s' % ver,
'.. deprecated:: %s' % ver]
hltext = ['.. versionadded:: %s' % version,
'.. versionchanged:: %s' % version,
'.. deprecated:: %s' % version]
def hl(no, line):
line = '<a name="L%s"> </a>' % no + escape(line)
@@ -881,11 +858,11 @@ class ChangesBuilder(Builder):
shutil.copyfile(path.join(path.dirname(__file__), 'style', 'default.css'),
path.join(self.outdir, 'default.css'))
def hl(self, text, ver):
def hl(self, text, version):
text = escape(text)
for directive in ['versionchanged', 'versionadded', 'deprecated']:
text = text.replace('.. %s:: %s' % (directive, ver),
'<b>.. %s:: %s</b>' % (directive, ver))
text = text.replace('.. %s:: %s' % (directive, version),
'<b>.. %s:: %s</b>' % (directive, version))
return text
def finish(self):
+103
View File
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""
sphinx.config
~~~~~~~~~~~~~
Build configuration file handling.
:copyright: 2008 by Georg Brandl.
:license: BSD license.
"""
import os
import sys
import types
from os import path
class ConfigError(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 'ConfigError(%r, %r)' % (self.message, self.orig_exc)
return 'ConfigError(%r)' % self.message
def __str__(self):
if self.orig_exc:
return '%s (exception: %s)' % (self.message, self.orig_exc)
return self.message
class Config(object):
"""Configuration file abstraction."""
# the values are: (default, needs fresh doctrees if changed)
config_values = dict(
# general substitutions
project = ('Python', True),
copyright = ('', False),
version = ('', True),
release = ('', True),
today = ('', True),
today_fmt = ('%B %d, %Y', True),
# extensibility
templates_path = ([], False),
extensions = ([], True),
# general reading options
unused_files = ([], True),
refcount_file = ('', True),
add_function_parentheses = (True, True),
add_module_names = (True, True),
# HTML options
html_last_updated_fmt = ('%b %d, %Y', False),
html_use_smartypants = (True, False),
html_translator_class = (None, False),
html_index = ('', False),
html_sidebars = ({}, False),
html_additional_pages = ({}, False),
# HTML help options
htmlhelp_basename = ('pydoc', False),
# LaTeX options
latex_paper_size = ('letter', False),
latex_font_size = ('10pt', False),
latex_documents = ([], False),
latex_preamble = ('', False),
latex_appendices = ([], False),
)
def __init__(self, dirname, filename):
config = {}
olddir = os.getcwd()
try:
os.chdir(dirname)
execfile(path.join(dirname, filename), config)
finally:
os.chdir(olddir)
# remove potentially pickling-problematic values
for key, val in config.items():
if key.startswith('_') or isinstance(val, types.ModuleType):
del config[key]
self.__dict__.update(config)
def __getattr__(self, name):
if name in self.config_values:
defval = self.config_values[name][0]
setattr(self, name, defval)
return defval
if name[0:1] == '_':
return object.__getattr__(self, name)
raise AttributeError('no configuration value named %r' % name)
def __getitem__(self, name):
return getattr(self, name)
+1 -1
View File
@@ -147,7 +147,7 @@ def parse_py_signature(signode, sig, desctype, env):
signode += addnodes.desc_classname(classname, classname)
# exceptions are a special case, since they are documented in the
# 'exceptions' module.
elif env.config.get('add_module_names', True) and \
elif env.config.add_module_names and \
env.currmodule and env.currmodule != 'exceptions':
nodetext = env.currmodule + '.'
signode += addnodes.desc_classname(nodetext, nodetext)
+34 -26
View File
@@ -22,6 +22,7 @@ try:
import hashlib
md5 = hashlib.md5
except:
# 2.4 compatibility
import md5
md5 = md5.new
@@ -102,10 +103,10 @@ class DefaultSubstitutions(Transform):
for ref in self.document.traverse(nodes.substitution_reference):
refname = ref['refname']
if refname in to_handle:
text = config.get(refname, '')
text = config[refname]
if refname == 'today' and not text:
# special handling: can also specify a strftime format
text = time.strftime(config.get('today_fmt', '%B %d, %Y'))
text = time.strftime(config.today_fmt)
ref.replace_self(nodes.Text(text, text))
@@ -186,7 +187,7 @@ class BuildEnvironment:
def __init__(self, srcdir, doctreedir):
self.doctreedir = doctreedir
self.srcdir = srcdir
self.config = {}
self.config = None
# refcount data if present
self.refcounts = {}
@@ -203,6 +204,7 @@ class BuildEnvironment:
# Build times -- to determine changed files
# Also use this as an inventory of all existing and built filenames.
# All "filenames" here are /-separated and relative and include '.rst'.
self.all_files = {} # filename -> (mtime, md5sum) at the time of build
# File metadata
@@ -279,7 +281,7 @@ class BuildEnvironment:
Return (added, changed, removed) iterables.
"""
all_source_files = list(get_matching_files(
self.srcdir, '*.rst', exclude=set(config.get('unused_files', ()))))
self.srcdir, '*.rst', exclude=set(config.unused_files)))
# clear all files no longer present
removed = set(self.all_files) - set(all_source_files)
@@ -312,27 +314,26 @@ class BuildEnvironment:
return added, changed, removed
# If one of these config values changes, all files need to be re-read.
influential_config_values = [
'version', 'release', 'today', 'today_fmt', 'unused_files',
'project', 'refcount_file', 'add_function_parentheses', 'add_module_names'
]
def update(self, config):
"""
(Re-)read all files new or changed since last update.
Yields a summary and then filenames as it processes them.
Store all environment filenames in the canonical format
(ie using SEP as a separator in place of os.path.sep).
"""
def update(self, config, hook=None):
"""(Re-)read all files new or changed since last update. Yields a summary
and then filenames as it processes them. Store all environment filenames
in the canonical format (ie using SEP as a separator in place of
os.path.sep)."""
config_changed = False
for val in self.influential_config_values:
if self.config.get(val) != config.get(val):
msg = '[config changed] '
config_changed = True
break
if self.config is None:
msg = '[new config] '
config_changed = True
else:
msg = ''
# check if a config value was changed that affects how doctrees are read
for key, descr in config.config_values.iteritems():
if not descr[1]:
continue
if self.config[key] != config[key]:
msg = '[config changed] '
config_changed = True
break
else:
msg = ''
added, changed, removed = self.get_outdated_files(config, config_changed)
msg += '%s added, %s changed, %s removed' % (len(added), len(changed),
len(removed))
@@ -341,9 +342,9 @@ class BuildEnvironment:
self.config = config
# read the refcounts file
if self.config.get('refcount_file'):
if self.config.refcount_file:
self.refcounts = Refcounts.fromfile(
path.join(self.srcdir, self.config['refcount_file']))
path.join(self.srcdir, self.config.refcount_file))
# clear all files no longer present
for filename in removed:
@@ -354,9 +355,12 @@ class BuildEnvironment:
yield filename
self.read_file(filename)
if 'contents.rst' not in self.all_files:
self._warnfunc('no master file contents.rst found')
# --------- SINGLE FILE BUILDING -------------------------------------------
def read_file(self, filename, src_path=None, save_parsed=True):
def read_file(self, filename, src_path=None, save_parsed=True, hook=None):
"""Parse a file and add/update inventory entries for the doctree.
If srcpath is given, read from a different source file."""
# remove all inventory entries for that file
@@ -382,6 +386,10 @@ class BuildEnvironment:
f.close()
self.all_files[filename] = (path.getmtime(src_path), md5sum)
# run post-read hook
if hook:
hook(doctree)
# make it picklable
doctree.reporter = None
doctree.transformer = None
+83
View File
@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""
sphinx.extension
~~~~~~~~~~~~~~~~
Gracefully adapted from the TextPress event system by Armin.
:copyright: 2008 by Georg Brandl, Armin Ronacher.
:license: BSD.
"""
from sphinx.config import ConfigError
def import_object(objname, source=None):
"""Import an object from a 'module.name' string."""
try:
module, name = objname.rsplit('.', 1)
except ValueError, err:
raise ConfigError('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 ConfigError('Could not import %s' % module +
(source and ' (needed for %s)' % source or ''), err)
except AttributeError, err:
raise ConfigError('Could not find %s' % objname +
(source and ' (needed for %s)' % source or ''), err)
# List of all known events. Maps name to arguments description.
events = {
'builder-created' : 'builder instance',
'doctree-read' : 'the doctree before being pickled',
}
class EventManager(object):
"""
Helper class that handles event listeners and events.
This is *not* a public interface. Always use the emit_event()
functions to access it or the connect_event() / disconnect_event()
functions on the application.
"""
def __init__(self):
self.next_listener_id = 0
self._listeners = {}
def _validate(self, event):
event = intern(event)
if event not in events:
raise RuntimeError('unknown event name: %s' % event)
def connect(self, event, callback):
self._validate(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 remove(self, listener_id):
for event in self._listeners:
event.pop(listener_id, None)
def emit(self, event, *args):
self._validate(event)
if event in self._listeners:
for listener_id, callback in self._listeners[event].iteritems():
yield listener_id, callback(*args)
class DummyEventManager(EventManager):
def connect(self, event, callback):
self._validate(event)
def remove(self, listener_id):
pass
def emit(self, event, *args):
self._validate(event)
+2 -2
View File
@@ -129,8 +129,8 @@ def build_hhx(builder, outdir, outname):
f = open(path.join(outdir, outname+'.hhp'), 'w')
try:
f.write(project_template % {'outname': outname,
'version': builder.config['version'],
'project': builder.config['project']})
'version': builder.config.version,
'project': builder.config.project})
if not outdir.endswith(os.sep):
outdir += os.sep
olen = len(outdir)
+252 -246
View File
@@ -18,7 +18,18 @@ from sphinx.util.smartypants import sphinx_smarty_pants
class HTMLWriter(Writer):
def __init__(self, builder):
Writer.__init__(self)
self.translator_class = translator_class(builder)
self.builder = builder
def translate(self):
# sadly, this is mostly copied from parent class
self.visitor = visitor = self.builder.translator_class(self.builder,
self.document)
self.document.walkabout(visitor)
self.output = visitor.astext()
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
'body_pre_docinfo', 'docinfo', 'body', 'fragment',
'body_suffix'):
setattr(self, attr, getattr(visitor, attr))
version_text = {
@@ -27,283 +38,278 @@ version_text = {
'versionadded': 'New in version %s',
}
def translator_class(builder):
class HTMLTranslator(BaseTranslator):
"""
Our custom HTML translator.
"""
class HTMLTranslator(BaseTranslator):
"""
Our custom HTML translator.
"""
def __init__(self, *args, **kwds):
self.no_smarty = 0
BaseTranslator.__init__(self, *args, **kwds)
self.highlightlang = 'python'
self.language.labels['warning'] = 'Caveat'
def __init__(self, builder, *args, **kwds):
BaseTranslator.__init__(self, *args, **kwds)
self.no_smarty = 0
self.builder = builder
self.highlightlang = 'python'
self.language.labels['warning'] = 'Caveat'
def visit_desc(self, node):
self.body.append(self.starttag(node, 'dl', CLASS=node['desctype']))
def depart_desc(self, node):
self.body.append('</dl>\n\n')
def visit_desc(self, node):
self.body.append(self.starttag(node, 'dl', CLASS=node['desctype']))
def depart_desc(self, node):
self.body.append('</dl>\n\n')
def visit_desc_signature(self, node):
# the id is set automatically
self.body.append(self.starttag(node, 'dt'))
# anchor for per-desc interactive data
if node.parent['desctype'] != 'describe' and node['ids'] and node['first']:
self.body.append('<!--#%s#-->' % node['ids'][0])
if node.parent['desctype'] in ('class', 'exception'):
self.body.append('%s ' % node.parent['desctype'])
def depart_desc_signature(self, node):
if node['ids'] and builder.name != 'htmlhelp':
self.body.append(u'<a class="headerlink" href="#%s" ' % node['ids'][0] +
u'title="Permalink to this definition">\u00B6</a>')
self.body.append('</dt>\n')
def visit_desc_signature(self, node):
# the id is set automatically
self.body.append(self.starttag(node, 'dt'))
# anchor for per-desc interactive data
if node.parent['desctype'] != 'describe' and node['ids'] and node['first']:
self.body.append('<!--#%s#-->' % node['ids'][0])
if node.parent['desctype'] in ('class', 'exception'):
self.body.append('%s ' % node.parent['desctype'])
def depart_desc_signature(self, node):
if node['ids'] and self.builder.name != 'htmlhelp':
self.body.append(u'<a class="headerlink" href="#%s" ' % node['ids'][0] +
u'title="Permalink to this definition">\u00B6</a>')
self.body.append('</dt>\n')
def visit_desc_classname(self, node):
self.body.append(self.starttag(node, 'tt', '', CLASS='descclassname'))
def depart_desc_classname(self, node):
self.body.append('</tt>')
def visit_desc_classname(self, node):
self.body.append(self.starttag(node, 'tt', '', CLASS='descclassname'))
def depart_desc_classname(self, node):
self.body.append('</tt>')
def visit_desc_type(self, node):
# return type of C functions -- nothing to do here
pass
def depart_desc_type(self, node):
pass
def visit_desc_type(self, node):
# return type of C functions -- nothing to do here
pass
def depart_desc_type(self, node):
pass
def visit_desc_name(self, node):
self.body.append(self.starttag(node, 'tt', '', CLASS='descname'))
def depart_desc_name(self, node):
self.body.append('</tt>')
def visit_desc_name(self, node):
self.body.append(self.starttag(node, 'tt', '', CLASS='descname'))
def depart_desc_name(self, node):
self.body.append('</tt>')
def visit_desc_parameterlist(self, node):
self.body.append('<big>(</big>')
self.first_param = 1
def depart_desc_parameterlist(self, node):
self.body.append('<big>)</big>')
def visit_desc_parameterlist(self, node):
self.body.append('<big>(</big>')
self.first_param = 1
def depart_desc_parameterlist(self, node):
self.body.append('<big>)</big>')
def visit_desc_parameter(self, node):
if not self.first_param:
self.body.append(', ')
else:
self.first_param = 0
if not node.hasattr('noemph'):
self.body.append('<em>')
def depart_desc_parameter(self, node):
if not node.hasattr('noemph'):
self.body.append('</em>')
def visit_desc_optional(self, node):
self.body.append('<span class="optional">[</span>')
def depart_desc_optional(self, node):
self.body.append('<span class="optional">]</span>')
def visit_desc_content(self, node):
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node):
self.body.append('</dd>')
def visit_refcount(self, node):
self.body.append(self.starttag(node, 'em', '', CLASS='refcount'))
def depart_refcount(self, node):
def visit_desc_parameter(self, node):
if not self.first_param:
self.body.append(', ')
else:
self.first_param = 0
if not node.hasattr('noemph'):
self.body.append('<em>')
def depart_desc_parameter(self, node):
if not node.hasattr('noemph'):
self.body.append('</em>')
def visit_versionmodified(self, node):
self.body.append(self.starttag(node, 'p'))
text = version_text[node['type']] % node['version']
if len(node):
text += ': '
def visit_desc_optional(self, node):
self.body.append('<span class="optional">[</span>')
def depart_desc_optional(self, node):
self.body.append('<span class="optional">]</span>')
def visit_desc_content(self, node):
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node):
self.body.append('</dd>')
def visit_refcount(self, node):
self.body.append(self.starttag(node, 'em', '', CLASS='refcount'))
def depart_refcount(self, node):
self.body.append('</em>')
def visit_versionmodified(self, node):
self.body.append(self.starttag(node, 'p'))
text = version_text[node['type']] % node['version']
if len(node):
text += ': '
else:
text += '.'
self.body.append('<span class="versionmodified">%s</span>' % text)
def depart_versionmodified(self, node):
self.body.append('</p>\n')
# overwritten
def visit_reference(self, node):
BaseTranslator.visit_reference(self, node)
if node.hasattr('reftitle'):
# ugly hack to add a title attribute
starttag = self.body[-1]
if not starttag.startswith('<a '):
return
self.body[-1] = '<a title="%s"' % self.attval(node['reftitle']) + \
starttag[2:]
# overwritten -- we don't want source comments to show up in the HTML
def visit_comment(self, node):
raise nodes.SkipNode
# overwritten
def visit_admonition(self, node, name=''):
self.body.append(self.start_tag_with_title(
node, 'div', CLASS=('admonition ' + name)))
if name and name != 'seealso':
node.insert(0, nodes.title(name, self.language.labels[name]))
self.set_first_last(node)
def visit_seealso(self, node):
self.visit_admonition(node, 'seealso')
def depart_seealso(self, node):
self.depart_admonition(node)
# overwritten
def visit_title(self, node, move_ids=1):
# if we have a section we do our own processing in order
# to have ids in the hN-tags and not in additional a-tags
if isinstance(node.parent, nodes.section):
h_level = self.section_level + self.initial_header_level - 1
if node.parent.get('ids'):
attrs = {'ids': node.parent['ids']}
else:
text += '.'
self.body.append('<span class="versionmodified">%s</span>' % text)
def depart_versionmodified(self, node):
self.body.append('</p>\n')
attrs = {}
self.body.append(self.starttag(node, 'h%d' % h_level, '', **attrs))
self.context.append('</h%d>\n' % h_level)
else:
BaseTranslator.visit_title(self, node, move_ids)
# overwritten
def visit_reference(self, node):
BaseTranslator.visit_reference(self, node)
if node.hasattr('reftitle'):
# ugly hack to add a title attribute
starttag = self.body[-1]
if not starttag.startswith('<a '):
return
self.body[-1] = '<a title="%s"' % self.attval(node['reftitle']) + \
starttag[2:]
# overwritten
def visit_literal_block(self, node):
from sphinx.highlighting import highlight_block
self.body.append(highlight_block(node.rawsource, self.highlightlang))
raise nodes.SkipNode
# overwritten -- we don't want source comments to show up in the HTML
def visit_comment(self, node):
raise nodes.SkipNode
# overwritten
def visit_literal(self, node):
if len(node.children) == 1 and \
node.children[0] in ('None', 'True', 'False'):
node['classes'].append('xref')
BaseTranslator.visit_literal(self, node)
# overwritten
def visit_admonition(self, node, name=''):
self.body.append(self.start_tag_with_title(
node, 'div', CLASS=('admonition ' + name)))
if name and name != 'seealso':
node.insert(0, nodes.title(name, self.language.labels[name]))
self.set_first_last(node)
def visit_seealso(self, node):
self.visit_admonition(node, 'seealso')
def depart_seealso(self, node):
self.depart_admonition(node)
# overwritten
def visit_title(self, node, move_ids=1):
# if we have a section we do our own processing in order
# to have ids in the hN-tags and not in additional a-tags
if isinstance(node.parent, nodes.section):
h_level = self.section_level + self.initial_header_level - 1
if node.parent.get('ids'):
attrs = {'ids': node.parent['ids']}
else:
attrs = {}
self.body.append(self.starttag(node, 'h%d' % h_level, '', **attrs))
self.context.append('</h%d>\n' % h_level)
def visit_productionlist(self, node):
self.body.append(self.starttag(node, 'pre'))
names = []
for production in node:
names.append(production['tokenname'])
maxlen = max(len(name) for name in names)
for production in node:
if production['tokenname']:
self.body.append(self.starttag(production, 'strong', ''))
self.body.append(production['tokenname'].ljust(maxlen) +
'</strong> ::= ')
lastname = production['tokenname']
else:
BaseTranslator.visit_title(self, node, move_ids)
self.body.append('%s ' % (' '*len(lastname)))
production.walkabout(self)
self.body.append('\n')
self.body.append('</pre>\n')
raise nodes.SkipNode
def depart_productionlist(self, node):
pass
# overwritten
def visit_literal_block(self, node):
from sphinx.highlighting import highlight_block
self.body.append(highlight_block(node.rawsource, self.highlightlang))
raise nodes.SkipNode
def visit_production(self, node):
pass
def depart_production(self, node):
pass
# overwritten
def visit_literal(self, node):
if len(node.children) == 1 and \
node.children[0] in ('None', 'True', 'False'):
node['classes'].append('xref')
BaseTranslator.visit_literal(self, node)
def visit_centered(self, node):
self.body.append(self.starttag(node, 'p', CLASS="centered") + '<strong>')
def depart_centered(self, node):
self.body.append('</strong></p>')
def visit_productionlist(self, node):
self.body.append(self.starttag(node, 'pre'))
names = []
for production in node:
names.append(production['tokenname'])
maxlen = max(len(name) for name in names)
for production in node:
if production['tokenname']:
self.body.append(self.starttag(production, 'strong', ''))
self.body.append(production['tokenname'].ljust(maxlen) +
'</strong> ::= ')
lastname = production['tokenname']
else:
self.body.append('%s ' % (' '*len(lastname)))
production.walkabout(self)
self.body.append('\n')
self.body.append('</pre>\n')
raise nodes.SkipNode
def depart_productionlist(self, node):
pass
def visit_compact_paragraph(self, node):
pass
def depart_compact_paragraph(self, node):
pass
def visit_production(self, node):
pass
def depart_production(self, node):
pass
def visit_highlightlang(self, node):
self.highlightlang = node['lang']
def depart_highlightlang(self, node):
pass
def visit_centered(self, node):
self.body.append(self.starttag(node, 'p', CLASS="centered") + '<strong>')
def depart_centered(self, node):
self.body.append('</strong></p>')
def visit_toctree(self, node):
# this only happens when formatting a toc from env.tocs -- in this
# case we don't want to include the subtree
raise nodes.SkipNode
def visit_compact_paragraph(self, node):
pass
def depart_compact_paragraph(self, node):
pass
def visit_index(self, node):
raise nodes.SkipNode
def visit_highlightlang(self, node):
self.highlightlang = node['lang']
def depart_highlightlang(self, node):
pass
def visit_glossary(self, node):
pass
def depart_glossary(self, node):
pass
def visit_toctree(self, node):
# this only happens when formatting a toc from env.tocs -- in this
# case we don't want to include the subtree
raise nodes.SkipNode
def visit_acks(self, node):
pass
def depart_acks(self, node):
pass
def visit_index(self, node):
raise nodes.SkipNode
def visit_module(self, node):
pass
def depart_module(self, node):
pass
def visit_glossary(self, node):
pass
def depart_glossary(self, node):
pass
# these are only handled specially in the SmartyPantsHTMLTranslator
def visit_literal_emphasis(self, node):
return self.visit_emphasis(node)
def depart_literal_emphasis(self, node):
return self.depart_emphasis(node)
def visit_acks(self, node):
pass
def depart_acks(self, node):
pass
def visit_module(self, node):
pass
def depart_module(self, node):
pass
# these are only handled specially in the SmartyPantsHTMLTranslator
def visit_literal_emphasis(self, node):
return self.visit_emphasis(node)
def depart_literal_emphasis(self, node):
return self.depart_emphasis(node)
def depart_title(self, node):
close_tag = self.context[-1]
if builder.name != 'htmlhelp' and \
(close_tag.startswith('</h') or
close_tag.startswith('</a></h')) and \
node.parent.hasattr('ids') and node.parent['ids']:
aname = node.parent['ids'][0]
# add permalink anchor
self.body.append(u'<a class="headerlink" href="#%s" ' % aname +
u'title="Permalink to this headline">\u00B6</a>')
BaseTranslator.depart_title(self, node)
def depart_title(self, node):
close_tag = self.context[-1]
if self.builder.name != 'htmlhelp' and \
(close_tag.startswith('</h') or
close_tag.startswith('</a></h')) and \
node.parent.hasattr('ids') and node.parent['ids']:
aname = node.parent['ids'][0]
# add permalink anchor
self.body.append(u'<a class="headerlink" href="#%s" ' % aname +
u'title="Permalink to this headline">\u00B6</a>')
BaseTranslator.depart_title(self, node)
class SmartyPantsHTMLTranslator(HTMLTranslator):
"""
Handle ordinary text via smartypants, converting quotes and dashes
to the correct entities.
"""
class SmartyPantsHTMLTranslator(HTMLTranslator):
"""
Handle ordinary text via smartypants, converting quotes and dashes
to the correct entities.
"""
def __init__(self, *args, **kwds):
self.no_smarty = 0
HTMLTranslator.__init__(self, *args, **kwds)
def __init__(self, *args, **kwds):
self.no_smarty = 0
HTMLTranslator.__init__(self, *args, **kwds)
def visit_literal(self, node):
self.no_smarty += 1
try:
# this raises SkipNode
HTMLTranslator.visit_literal(self, node)
finally:
self.no_smarty -= 1
def visit_literal_emphasis(self, node):
self.no_smarty += 1
self.visit_emphasis(node)
def depart_literal_emphasis(self, node):
self.depart_emphasis(node)
def visit_literal(self, node):
self.no_smarty += 1
try:
# this raises SkipNode
HTMLTranslator.visit_literal(self, node)
finally:
self.no_smarty -= 1
def visit_desc_signature(self, node):
self.no_smarty += 1
HTMLTranslator.visit_desc_signature(self, node)
def visit_literal_emphasis(self, node):
self.no_smarty += 1
self.visit_emphasis(node)
def depart_desc_signature(self, node):
def depart_literal_emphasis(self, node):
self.depart_emphasis(node)
self.no_smarty -= 1
def visit_desc_signature(self, node):
self.no_smarty += 1
HTMLTranslator.visit_desc_signature(self, node)
def depart_desc_signature(self, node):
self.no_smarty -= 1
HTMLTranslator.depart_desc_signature(self, node)
def visit_productionlist(self, node):
self.no_smarty += 1
try:
HTMLTranslator.visit_productionlist(self, node)
finally:
self.no_smarty -= 1
HTMLTranslator.depart_desc_signature(self, node)
def visit_productionlist(self, node):
self.no_smarty += 1
try:
HTMLTranslator.visit_productionlist(self, node)
finally:
self.no_smarty -= 1
def encode(self, text):
text = HTMLTranslator.encode(self, text)
if self.no_smarty <= 0:
text = sphinx_smarty_pants(text)
return text
if builder.config.get('html_use_smartypants', False):
return SmartyPantsHTMLTranslator
else:
return HTMLTranslator
def encode(self, text):
text = HTMLTranslator.encode(self, text)
if self.no_smarty <= 0:
text = sphinx_smarty_pants(text)
return text
+6 -6
View File
@@ -21,7 +21,7 @@ from docutils import frontend, nodes, languages, writers, utils
from sphinx import addnodes
from sphinx import highlighting
# Move to a template?
HEADER = r'''%% Generated by Sphinx.
\documentclass[%(papersize)s,%(pointsize)s]{%(docclass)s}
\usepackage[utf8]{inputenc}
@@ -92,18 +92,18 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.builder = builder
self.body = []
docclass = document.settings.docclass
paper = builder.config.get('latex_paper_size', 'letter') + 'paper'
paper = builder.config.latex_paper_size + 'paper'
if paper == 'paper': # e.g. command line "-D latex_paper_size="
paper = 'letterpaper'
date = time.strftime(builder.config.get('today_fmt', '%B %d, %Y'))
date = time.strftime(builder.config.today_fmt)
self.options = {'docclass': docclass,
'papersize': paper,
'pointsize': builder.config.get('latex_font_size', '10pt'),
'preamble': builder.config['latex_preamble'],
'pointsize': builder.config.latex_font_size,
'preamble': builder.config.latex_preamble,
'author': document.settings.author,
'filename': document.settings.filename,
'title': None, # is determined later
'release': builder.config['release'],
'release': builder.config.release,
'date': date,
}
self.context = []
-60
View File
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""
sphinx.patchlevel
~~~~~~~~~~~~~~~~~
Extract version info from Include/patchlevel.h.
Adapted from Doc/tools/getversioninfo.
XXX Python specific
:copyright: 2007-2008 by Georg Brandl.
:license: BSD.
"""
import os
import re
import sys
def get_version_info(srcdir):
patchlevel_h = os.path.join(srcdir, '..', "Include", "patchlevel.h")
# This won't pick out all #defines, but it will pick up the ones we
# care about.
rx = re.compile(r"\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)")
d = {}
f = open(patchlevel_h)
try:
for line in f:
m = rx.match(line)
if m is not None:
name, value = m.group(1, 2)
d[name] = value
finally:
f.close()
release = version = "%s.%s" % (d["PY_MAJOR_VERSION"], d["PY_MINOR_VERSION"])
micro = int(d["PY_MICRO_VERSION"])
if micro != 0:
release += "." + str(micro)
level = d["PY_RELEASE_LEVEL"]
suffixes = {
"PY_RELEASE_LEVEL_ALPHA": "a",
"PY_RELEASE_LEVEL_BETA": "b",
"PY_RELEASE_LEVEL_GAMMA": "c",
}
if level != "PY_RELEASE_LEVEL_FINAL":
release += suffixes[level] + str(int(d["PY_RELEASE_SERIAL"]))
return version, release
def get_sys_version_info():
major, minor, micro, level, serial = sys.version_info
release = version = '%s.%s' % (major, minor)
if micro:
release += '.%s' % micro
if level != 'final':
release += '%s%s' % (level[0], serial)
return version, release
+1 -1
View File
@@ -109,7 +109,7 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
if text.endswith('()'):
# remove parentheses
text = text[:-2]
if env.config.get('add_function_parentheses', True):
if env.config.add_function_parentheses:
# add them back to all occurrences if configured
text += '()'
# if the first character is a bang, don't cross-reference at all
+2 -1
View File
@@ -113,7 +113,8 @@
{% if customsidebar %}
{{ rendertemplate(customsidebar) }}
{% endif %}
{% if current_page_name != "search" %}
{% if current_page_name != "search" and builder == 'web' %}
{# HTML builder search is disabled for now #}
<h3>{{ builder == 'web' and 'Keyword' or 'Quick' }} search</h3>
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" size="18"> <input type="submit" value="Go">