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 Changes in trunk
================ ================
* sphinx.application: Support a new method, ``add_crossref_type``. New features added
It works like ``add_description_unit`` but the directive will only ------------------
create a target and no output.
* sphinx.application: Support a new method, ``add_transform``. * Extension API (Application object):
It takes a standard docutils ``Transform`` subclass which is then
applied by Sphinx' reader on parsing reST document trees.
* sphinx.directives: New directive, ``currentmodule``. It can be used - Support a new method, ``add_crossref_type``. It works like
to indicate the module name of the following documented things without ``add_description_unit`` but the directive will only create a target
creating index entries. 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 * sphinx.ext.autodoc: Don't check ``__module__`` for explicitly given
members. Remove "self" in class constructor argument list. members. Remove "self" in class constructor argument list.
* sphinx.environment: Don't swallow TOC entries when resolving subtrees. * sphinx.htmlwriter: Don't use os.path for joining image HREFs.
* 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.roles: Fix referencing glossary terms with explicit targets. * 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. 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) 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 ``'sphinx'``, which is a builtin style designed to match Sphinx' default
style. 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: .. _html-options:
@ -211,6 +219,13 @@ that use Sphinx' HTMLWriter class.
If true, the reST sources are included in the HTML build as If true, the reST sources are included in the HTML build as
:file:`_sources/{name}`. :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 .. confval:: htmlhelp_basename
Output file base name for HTML help builder. Default is ``'pydoc'``. Output file base name for HTML help builder. Default is ``'pydoc'``.

View File

@ -3,6 +3,8 @@
Extension API Extension API
============= =============
.. currentmodule:: sphinx.application
Each Sphinx extension is a Python module with at least a :func:`setup` function. 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 This function is called at initialization time with one argument, the
application object representing the Sphinx process. This application object has application object representing the Sphinx process. This application object has
@ -169,3 +171,11 @@ Event name Emitted when Arguments
references and TOCs have been references and TOCs have been
inserted 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: 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 * You can :ref:`write a custom builder <writing-builders>` that derives from
:class:`~sphinx.builder.StandaloneHTMLBuilder` and calls your template engine :class:`~sphinx.builder.StandaloneHTMLBuilder` and calls your template engine
of choice. of choice.
@ -36,4 +40,6 @@ Inheritance is done via two (Jinja) directives, ``extends`` and ``block``.
blocks blocks
extends !template 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. 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): if sys.version_info < (2, 4):
print 'ERROR: Sphinx requires at least Python 2.4 to run.' print 'ERROR: Sphinx requires at least Python 2.4 to run.'

View File

@ -13,7 +13,8 @@ import sys
import codecs import codecs
from os import path 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 import Environment
from jinja.loaders import BaseLoader from jinja.loaders import BaseLoader
@ -53,3 +54,27 @@ class SphinxFileSystemLoader(BaseLoader):
return f.read() return f.read()
finally: finally:
f.close() 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): def add_transform(self, transform):
SphinxStandaloneReader.transforms.append(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 raise NotImplementedError
def init_templates(self): def init_templates(self):
"""Call if you need Jinja templates in the builder.""" # Call this from init() if you need templates.
# lazily import this, other builders won't need it if self.config.template_bridge:
from sphinx._jinja import Environment, SphinxFileSystemLoader self.templates = self.app.import_object(
self.config.template_bridge, 'template_bridge setting')()
# load templates else:
self.templates = {} from sphinx._jinja import BuiltinTemplates
base_templates_path = path.join(path.dirname(__file__), 'templates') self.templates = BuiltinTemplates()
ext_templates_path = [path.join(self.srcdir, dir) self.templates.init(self)
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
def get_target_uri(self, docname, typ=None): def get_target_uri(self, docname, typ=None):
""" """
@ -514,8 +501,8 @@ class StandaloneHTMLBuilder(Builder):
self.handle_finish() self.handle_finish()
def get_outdated_docs(self): def get_outdated_docs(self):
if self.templates_path: if self.templates:
template_mtime = max(mtimes_of_files(self.templates_path, '.html')) template_mtime = self.templates.newest_template_mtime()
else: else:
template_mtime = 0 template_mtime = 0
for docname in self.env.found_docs: for docname in self.env.found_docs:
@ -535,7 +522,7 @@ class StandaloneHTMLBuilder(Builder):
except EnvironmentError: except EnvironmentError:
# source doesn't exist anymore # source doesn't exist anymore
pass pass
def load_indexer(self, docnames): def load_indexer(self, docnames):
try: try:
f = open(path.join(self.outdir, 'searchindex.'+self.indexer_format), 'r') f = open(path.join(self.outdir, 'searchindex.'+self.indexer_format), 'r')
@ -574,7 +561,7 @@ class StandaloneHTMLBuilder(Builder):
ctx['customsidebar'] = sidebarfile ctx['customsidebar'] = sidebarfile
ctx.update(addctx) 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') outfilename = path.join(self.outdir, os_path(pagename) + '.html')
ensuredir(path.dirname(outfilename)) # normally different from self.outdir ensuredir(path.dirname(outfilename)) # normally different from self.outdir
try: try:
@ -611,8 +598,7 @@ class PickleHTMLBuilder(StandaloneHTMLBuilder):
def init(self): def init(self):
self.init_translator_class() self.init_translator_class()
# no templates used, but get_outdated_docs() needs this attribute self.templates = None # no template bridge necessary
self.templates_path = []
def get_target_uri(self, docname, typ=None): def get_target_uri(self, docname, typ=None):
if docname == 'index': if docname == 'index':
@ -827,9 +813,6 @@ class ChangesBuilder(Builder):
def init(self): def init(self):
self.init_templates() 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): def get_outdated_docs(self):
return self.outdir return self.outdir
@ -885,12 +868,12 @@ class ChangesBuilder(Builder):
} }
f = open(path.join(self.outdir, 'index.html'), 'w') f = open(path.join(self.outdir, 'index.html'), 'w')
try: try:
f.write(self.ftemplate.render(ctx)) f.write(self.templates.render('changes/frameset.html', ctx))
finally: finally:
f.close() f.close()
f = open(path.join(self.outdir, 'changes.html'), 'w') f = open(path.join(self.outdir, 'changes.html'), 'w')
try: try:
f.write(self.vtemplate.render(ctx)) f.write(self.templates.render('changes/versionchanges.html', ctx))
finally: finally:
f.close() f.close()
@ -916,7 +899,7 @@ class ChangesBuilder(Builder):
try: try:
text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines)) text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
ctx = {'filename': self.env.doc2path(docname, None), 'text': text} 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: finally:
f.close() f.close()
shutil.copyfile(path.join(path.dirname(__file__), 'static', 'default.css'), shutil.copyfile(path.join(path.dirname(__file__), 'static', 'default.css'),

View File

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