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

This commit is contained in:
Georg Brandl 2008-04-13 18:16:55 +00:00
parent 9e485e078e
commit ae8813c788
9 changed files with 137 additions and 61 deletions

57
CHANGES
View File

@ -1,42 +1,49 @@
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)
================================

View File

@ -132,6 +132,14 @@ General configuration
``'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:
@ -211,6 +219,13 @@ that use Sphinx' HTMLWriter class.
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'``.

View File

@ -3,6 +3,8 @@
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
@ -169,3 +171,11 @@ Event name Emitted when Arguments
references and TOCs have been
inserted
====================== =================================== =========
.. _template-bridge:
The template bridge
-------------------
.. autoclass:: TemplateBridge
:members:

View File

@ -14,6 +14,10 @@ Do I need to use Sphinx' templates to produce HTML?
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.
@ -36,4 +40,6 @@ Inheritance is done via two (Jinja) directives, ``extends`` and ``block``.
blocks
extends !template
XXX continue this
template names for other template engines
.. XXX continue this

View File

@ -34,7 +34,7 @@ are already present, work fine and can be seen "in action" in the Python docs:
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.'

View File

@ -13,7 +13,8 @@ import sys
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
@ -53,3 +54,27 @@ class SphinxFileSystemLoader(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)

View File

@ -217,3 +217,32 @@ class Sphinx(object):
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')

View File

@ -74,27 +74,14 @@ class Builder(object):
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):
"""
@ -514,8 +501,8 @@ class StandaloneHTMLBuilder(Builder):
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:
@ -574,7 +561,7 @@ class StandaloneHTMLBuilder(Builder):
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:
@ -611,8 +598,7 @@ class PickleHTMLBuilder(StandaloneHTMLBuilder):
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':
@ -827,9 +813,6 @@ class ChangesBuilder(Builder):
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
@ -885,12 +868,12 @@ class ChangesBuilder(Builder):
}
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()
@ -916,7 +899,7 @@ class ChangesBuilder(Builder):
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'),

View File

@ -42,6 +42,7 @@ class Config(object):
add_module_names = (True, True),
show_authors = (False, True),
pygments_style = ('sphinx', False),
template_bridge = (None, False),
# HTML options
html_style = ('default.css', False),