[Python-checkins] r60174 - in doctools/trunk/sphinx: __init__.py builder.py config.py directives.py environment.py extension.py htmlhelp.py htmlwriter.py latexwriter.py patchlevel.py roles.py templates/layout.html

georg.brandl python-checkins at python.org
Mon Jan 21 21:20:38 CET 2008


Author: georg.brandl
Date: Mon Jan 21 21:20:37 2008
New Revision: 60174

Added:
   doctools/trunk/sphinx/config.py
   doctools/trunk/sphinx/extension.py
Removed:
   doctools/trunk/sphinx/patchlevel.py
Modified:
   doctools/trunk/sphinx/__init__.py
   doctools/trunk/sphinx/builder.py
   doctools/trunk/sphinx/directives.py
   doctools/trunk/sphinx/environment.py
   doctools/trunk/sphinx/htmlhelp.py
   doctools/trunk/sphinx/htmlwriter.py
   doctools/trunk/sphinx/latexwriter.py
   doctools/trunk/sphinx/roles.py
   doctools/trunk/sphinx/templates/layout.html
Log:
Further refactorings, add extensibility API.


Modified: doctools/trunk/sphinx/__init__.py
==============================================================================
--- doctools/trunk/sphinx/__init__.py	(original)
+++ doctools/trunk/sphinx/__init__.py	Mon Jan 21 21:20:37 2008
@@ -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 @@
 
 
 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 @@
     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 @@
         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

Modified: doctools/trunk/sphinx/builder.py
==============================================================================
--- doctools/trunk/sphinx/builder.py	(original)
+++ doctools/trunk/sphinx/builder.py	Mon Jan 21 21:20:37 2008
@@ -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.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 @@
 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 @@
         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 @@
         """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 @@
         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 @@
 
     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 @@
 
         # 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 @@
         )
 
     def write_file(self, filename, doctree):
+        pagename = filename[:-4]
         destination = StringOutput(encoding='utf-8')
         doctree.settings = self.docsettings
 
@@ -366,7 +344,7 @@
         else:
             title = ''
         self.globalcontext['titles'][filename] = title
-        sourcename = filename[:-4] + '.txt'
+        sourcename = pagename + '.txt'
         context = dict(
             title = title,
             sourcename = sourcename,
@@ -379,8 +357,8 @@
             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 @@
         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 @@
 
     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 @@
         # 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 @@
                 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 @@
 
     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 @@
         # 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 @@
     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 @@
 
     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 @@
     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 @@
     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 @@
     }
 
     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 @@
                     (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 @@
         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 @@
         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):

Added: doctools/trunk/sphinx/config.py
==============================================================================
--- (empty file)
+++ doctools/trunk/sphinx/config.py	Mon Jan 21 21:20:37 2008
@@ -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)

Modified: doctools/trunk/sphinx/directives.py
==============================================================================
--- doctools/trunk/sphinx/directives.py	(original)
+++ doctools/trunk/sphinx/directives.py	Mon Jan 21 21:20:37 2008
@@ -147,7 +147,7 @@
         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)

Modified: doctools/trunk/sphinx/environment.py
==============================================================================
--- doctools/trunk/sphinx/environment.py	(original)
+++ doctools/trunk/sphinx/environment.py	Mon Jan 21 21:20:37 2008
@@ -22,6 +22,7 @@
     import hashlib
     md5 = hashlib.md5
 except:
+    # 2.4 compatibility
     import md5
     md5 = md5.new
 
@@ -102,10 +103,10 @@
         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 @@
     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 @@
 
         # 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 @@
         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 @@
 
         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 @@
         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 @@
             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 @@
             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

Added: doctools/trunk/sphinx/extension.py
==============================================================================
--- (empty file)
+++ doctools/trunk/sphinx/extension.py	Mon Jan 21 21:20:37 2008
@@ -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)

Modified: doctools/trunk/sphinx/htmlhelp.py
==============================================================================
--- doctools/trunk/sphinx/htmlhelp.py	(original)
+++ doctools/trunk/sphinx/htmlhelp.py	Mon Jan 21 21:20:37 2008
@@ -129,8 +129,8 @@
     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)

Modified: doctools/trunk/sphinx/htmlwriter.py
==============================================================================
--- doctools/trunk/sphinx/htmlwriter.py	(original)
+++ doctools/trunk/sphinx/htmlwriter.py	Mon Jan 21 21:20:37 2008
@@ -18,7 +18,18 @@
 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 @@
     'versionadded': 'New in version %s',
 }
 
-def translator_class(builder):
-    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 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_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_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_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):
+class HTMLTranslator(BaseTranslator):
+    """
+    Our custom HTML translator.
+    """
+
+    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_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_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_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_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')
-
-        # 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:
-                    attrs = {}
-                self.body.append(self.starttag(node, 'h%d' % h_level, '', **attrs))
-                self.context.append('</h%d>\n' % h_level)
+                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_literal_block(self, node):
+        from sphinx.highlighting import highlight_block
+        self.body.append(highlight_block(node.rawsource, self.highlightlang))
+        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)
+
+    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)
-
-        # 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
-        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_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_production(self, node):
-            pass
-        def depart_production(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_compact_paragraph(self, node):
-            pass
-        def depart_compact_paragraph(self, node):
-            pass
-
-        def visit_highlightlang(self, node):
-            self.highlightlang = node['lang']
-        def depart_highlightlang(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_index(self, node):
-            raise nodes.SkipNode
-
-        def visit_glossary(self, node):
-            pass
-        def depart_glossary(self, node):
-            pass
-
-        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)
-
-
-    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 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)
+                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_production(self, node):
+        pass
+    def depart_production(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_compact_paragraph(self, node):
+        pass
+    def depart_compact_paragraph(self, node):
+        pass
+
+    def visit_highlightlang(self, node):
+        self.highlightlang = node['lang']
+    def depart_highlightlang(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_index(self, node):
+        raise nodes.SkipNode
+
+    def visit_glossary(self, node):
+        pass
+    def depart_glossary(self, node):
+        pass
+
+    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 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.
+    """
+
+    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_desc_signature(self, node):
-            self.no_smarty += 1
-            HTMLTranslator.visit_desc_signature(self, node)
-
-        def depart_desc_signature(self, node):
+    def visit_literal_emphasis(self, node):
+        self.no_smarty += 1
+        self.visit_emphasis(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

Modified: doctools/trunk/sphinx/latexwriter.py
==============================================================================
--- doctools/trunk/sphinx/latexwriter.py	(original)
+++ doctools/trunk/sphinx/latexwriter.py	Mon Jan 21 21:20:37 2008
@@ -21,7 +21,7 @@
 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 @@
         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 = []

Deleted: /doctools/trunk/sphinx/patchlevel.py
==============================================================================
--- /doctools/trunk/sphinx/patchlevel.py	Mon Jan 21 21:20:37 2008
+++ (empty 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

Modified: doctools/trunk/sphinx/roles.py
==============================================================================
--- doctools/trunk/sphinx/roles.py	(original)
+++ doctools/trunk/sphinx/roles.py	Mon Jan 21 21:20:37 2008
@@ -109,7 +109,7 @@
         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

Modified: doctools/trunk/sphinx/templates/layout.html
==============================================================================
--- doctools/trunk/sphinx/templates/layout.html	(original)
+++ doctools/trunk/sphinx/templates/layout.html	Mon Jan 21 21:20:37 2008
@@ -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">


More information about the Python-checkins mailing list