diff --git a/CHANGES b/CHANGES index de562da6b..a11d101ae 100644 --- a/CHANGES +++ b/CHANGES @@ -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) ================================ diff --git a/doc/config.rst b/doc/config.rst index 91dd8b064..938af23f9 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -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'``. diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index 3cbdf0533..adf80fdc0 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -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: diff --git a/doc/templating.rst b/doc/templating.rst index 0befdbe3a..b44864140 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -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 ` 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 diff --git a/setup.py b/setup.py index cbb0074dc..7ab333ba8 100644 --- a/setup.py +++ b/setup.py @@ -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.' diff --git a/sphinx/_jinja.py b/sphinx/_jinja.py index 1cb5f5500..e35b1e5be 100644 --- a/sphinx/_jinja.py +++ b/sphinx/_jinja.py @@ -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) diff --git a/sphinx/application.py b/sphinx/application.py index 5ea08edef..c86fd074e 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -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') diff --git a/sphinx/builder.py b/sphinx/builder.py index 9e00f222a..59cf3174b 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -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: @@ -535,7 +522,7 @@ class StandaloneHTMLBuilder(Builder): 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') @@ -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'), diff --git a/sphinx/config.py b/sphinx/config.py index 055cc61e6..e4fbddf49 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -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),