Commits

Anonymous committed 4976feb

Add new templating API, remove Jinja external and add it to setup.py dependencies.

  • Participants
  • Parent commits 00ba093

Comments (0)

Files changed (9)

 Changes in trunk
 ================
 
-* sphinx.application: Support a new method, ``add_crossref_type``.
-  It works like ``add_description_unit`` but the directive will only
-  create a target and no output.
+New features added
+------------------
 
-* sphinx.application: Support a new method, ``add_transform``.
-  It takes a standard docutils ``Transform`` subclass which is then
-  applied by Sphinx' reader on parsing reST document trees.
+* Extension API (Application object):
 
-* sphinx.directives: New directive, ``currentmodule``.  It can be used
-  to indicate the module name of the following documented things without
-  creating index entries.
+  - Support a new method, ``add_crossref_type``.  It works like
+    ``add_description_unit`` but the directive will only create a target
+    and no output.
+  - Support a new method, ``add_transform``.  It takes a standard docutils
+    ``Transform`` subclass which is then applied by Sphinx' reader on
+    parsing reST document trees.
+  - Add support for other template engines than Jinja, by adding an
+    abstraction called a "template bridge".  This class handles rendering
+    of templates and can be changed using the new configuration value
+    "template_bridge".
+  - The config file itself can be an extension (if it provides a ``setup()``
+    function).
+
+* Markup:
+
+  - New directive, ``currentmodule``.  It can be used to indicate the module
+    name of the following documented things without creating index entries.
+  - Allow giving a different title to documents in the toctree.
+  - Allow giving multiple options in a ``cmdoption`` directive.
+  - Fix display of class members without explicit class name given.
+
+Thanks to Jacob Kaplan-Moss, Talin and Sebastian Wiesner for suggestions.
+
+Bugs fixed
+----------
 
 * sphinx.ext.autodoc: Don't check ``__module__`` for explicitly given
   members.  Remove "self" in class constructor argument list.
 
-* sphinx.environment: Don't swallow TOC entries when resolving subtrees.
-
-* sphinx.directives: Allow giving a different title to documents
-  in the toctree.
-
-* sphinx.directives: Allow giving multiple options in a ``cmdoption``
-  directive.
-
-* sphinx.directives: Fix display of class members without explicit
-  class name given.
+* sphinx.htmlwriter: Don't use os.path for joining image HREFs.
 
 * sphinx.roles: Fix referencing glossary terms with explicit targets.
 
-* sphinx.builder, sphinx.environment: Gracefully handle some exception
+* sphinx.environment: Don't swallow TOC entries when resolving subtrees.
+
+* sphinx.builder, sphinx.environment: Gracefully handle some user error
   cases.
 
-* sphinx.config: The config file itself can be an extension (if it
-  provides a setup() function).
-
-* sphinx.htmlwriter: Don't use os.path for joining image HREFs.
-
 
 Release 0.1.61950 (Mar 26, 2008)
 ================================
    ``'sphinx'``, which is a builtin style designed to match Sphinx' default
    style.
 
+.. confval:: template_bridge
+
+   A string with the fully-qualified (that is, including the module name) name
+   of a callable (or simply a class) that returns an instance of
+   :class:`~sphinx.application.TemplateBridge`.  This instance is then used to
+   render HTML documents, and possibly the output of other builders (currently
+   the changes builder).
+
 
 .. _html-options:
 
    If true, the reST sources are included in the HTML build as
    :file:`_sources/{name}`.
 
+.. confval:: html_translator_class
+
+   A string with the fully-qualified (that is, including the module name) name
+   of a HTML Translator class, that is, a subclass of Sphinx'
+   :class:`~sphinx.htmlwriter.HTMLTranslator`, that is used to translate
+   document trees to HTML.  Default is ``None`` (use the builtin translator).
+
 .. confval:: htmlhelp_basename
 
    Output file base name for HTML help builder.  Default is ``'pydoc'``.

doc/ext/appapi.rst

 Extension API
 =============
 
+.. currentmodule:: sphinx.application
+
 Each Sphinx extension is a Python module with at least a :func:`setup` function.
 This function is called at initialization time with one argument, the
 application object representing the Sphinx process.  This application object has
                        references and TOCs have been
                        inserted
 ====================== =================================== =========
+
+.. _template-bridge:
+
+The template bridge
+-------------------
+
+.. autoclass:: TemplateBridge
+   :members:

doc/templating.rst

 
 No.  You have several other options:
 
+* You can write a :class:`~sphinx.application.TemplateBridge` subclass that
+  calls your template engine of choice, and set the :confval:`template_bridge`
+  configuration value accordingly.
+
 * You can :ref:`write a custom builder <writing-builders>` that derives from
   :class:`~sphinx.builder.StandaloneHTMLBuilder` and calls your template engine
   of choice.
    blocks
    extends !template
 
-XXX continue this
+   template names for other template engines
+
+.. XXX continue this
   and inclusion of appropriately formatted docstrings.
 '''
 
-requires = ['Pygments>=0.8', 'docutils>=0.4']
+requires = ['Pygments>=0.8', 'Jinja>=1.1', 'docutils>=0.4']
 
 if sys.version_info < (2, 4):
     print 'ERROR: Sphinx requires at least Python 2.4 to run.'
 import codecs
 from os import path
 
-sys.path.insert(0, path.dirname(__file__))
+from sphinx.util import mtimes_of_files
+from sphinx.application import TemplateBridge
 
 from jinja import Environment
 from jinja.loaders import BaseLoader
             return f.read()
         finally:
             f.close()
+
+
+class BuiltinTemplates(TemplateBridge):
+    def init(self, builder):
+        self.templates = {}
+        base_templates_path = path.join(path.dirname(__file__), 'templates')
+        ext_templates_path = [path.join(builder.srcdir, dir)
+                              for dir in builder.config.templates_path]
+        self.templates_path = [base_templates_path] + ext_templates_path
+        loader = SphinxFileSystemLoader(base_templates_path, ext_templates_path)
+        self.jinja_env = Environment(loader=loader,
+                                     # disable traceback, more likely that something
+                                     # in the application is broken than in the templates
+                                     friendly_traceback=False)
+
+    def newest_template_mtime(self):
+        return max(mtimes_of_files(self.templates_path, '.html'))
+
+    def render(self, template, context):
+        if template in self.templates:
+            return self.templates[template].render(context)
+        templateobj = self.templates[template] = \
+                      self.jinja_env.get_template(template)
+        return templateobj.render(context)

sphinx/application.py

 
     def add_transform(self, transform):
         SphinxStandaloneReader.transforms.append(transform)
+
+
+class TemplateBridge(object):
+    """
+    
+    """
+
+    def init(self, builder):
+        """
+        Called by the builder to initialize the template system.  *builder*
+        is the builder object; you'll probably want to look at the value of
+        ``builder.config.templates_path``.
+        """
+        raise NotImplementedError('must be implemented in subclasses')
+
+    def newest_template_mtime(self):
+        """
+        Called by the builder to determine if output files are outdated
+        because of template changes.  Return the mtime of the newest template
+        file that was changed.  The default implementation returns ``0``.
+        """
+        return 0
+
+    def render(self, template, context):
+        """
+        Called by the builder to render a *template* with a specified
+        context (a Python dictionary).
+        """
+        raise NotImplementedError('must be implemented in subclasses')

sphinx/builder.py

         raise NotImplementedError
 
     def init_templates(self):
-        """Call if you need Jinja templates in the builder."""
-        # lazily import this, other builders won't need it
-        from sphinx._jinja import Environment, SphinxFileSystemLoader
-
-        # load templates
-        self.templates = {}
-        base_templates_path = path.join(path.dirname(__file__), 'templates')
-        ext_templates_path = [path.join(self.srcdir, dir)
-                              for dir in self.config.templates_path]
-        self.templates_path = [base_templates_path] + ext_templates_path
-        loader = SphinxFileSystemLoader(base_templates_path, ext_templates_path)
-        self.jinja_env = Environment(loader=loader,
-                                     # 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
+        # Call this from init() if you need templates.
+        if self.config.template_bridge:
+            self.templates = self.app.import_object(
+                self.config.template_bridge, 'template_bridge setting')()
+        else:
+            from sphinx._jinja import BuiltinTemplates
+            self.templates = BuiltinTemplates()
+        self.templates.init(self)
 
     def get_target_uri(self, docname, typ=None):
         """
         self.handle_finish()
 
     def get_outdated_docs(self):
-        if self.templates_path:
-            template_mtime = max(mtimes_of_files(self.templates_path, '.html'))
+        if self.templates:
+            template_mtime = self.templates.newest_template_mtime()
         else:
             template_mtime = 0
         for docname in self.env.found_docs:
             except EnvironmentError:
                 # source doesn't exist anymore
                 pass
-                
+
     def load_indexer(self, docnames):
         try:
             f = open(path.join(self.outdir, 'searchindex.'+self.indexer_format), 'r')
             ctx['customsidebar'] = sidebarfile
         ctx.update(addctx)
 
-        output = self.get_template(templatename).render(ctx)
+        output = self.templates.render(templatename, ctx)
         outfilename = path.join(self.outdir, os_path(pagename) + '.html')
         ensuredir(path.dirname(outfilename)) # normally different from self.outdir
         try:
 
     def init(self):
         self.init_translator_class()
-        # no templates used, but get_outdated_docs() needs this attribute
-        self.templates_path = []
+        self.templates = None   # no template bridge necessary
 
     def get_target_uri(self, docname, typ=None):
         if docname == 'index':
 
     def init(self):
         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_docs(self):
         return self.outdir
         }
         f = open(path.join(self.outdir, 'index.html'), 'w')
         try:
-            f.write(self.ftemplate.render(ctx))
+            f.write(self.templates.render('changes/frameset.html', ctx))
         finally:
             f.close()
         f = open(path.join(self.outdir, 'changes.html'), 'w')
         try:
-            f.write(self.vtemplate.render(ctx))
+            f.write(self.templates.render('changes/versionchanges.html', ctx))
         finally:
             f.close()
 
             try:
                 text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
                 ctx = {'filename': self.env.doc2path(docname, None), 'text': text}
-                f.write(self.stemplate.render(ctx))
+                f.write(self.templates.render('changes/rstsource.html', ctx))
             finally:
                 f.close()
         shutil.copyfile(path.join(path.dirname(__file__), 'static', 'default.css'),
         add_module_names = (True, True),
         show_authors = (False, True),
         pygments_style = ('sphinx', False),
+        template_bridge = (None, False),
 
         # HTML options
         html_style = ('default.css', False),