Edgewall Software

source: trunk/genshi/template/plugin.py

Last change on this file was 1160, checked in by hodgestar, 13 years ago

Merge r1143 from py3k:

add support for python 3 to remaining genshi.template components:

  • minor changes to track encoding=None API change in core genshi modules.
  • genshi/template/directives:
    • slightly odd syntax changes to make the 2to3 .next() fixer pick up *stream.next()
    • minor test fix for change in behaviour of division (/) in Python 3.
  • genshi/template/loader:
    • add 'b' to file modes to ensure it's loaded as bytes in Python 3.
  • use not isinstance(s, unicode) instead of isinstance(s, str) since the former is correctly converted by 2to3.
  • Property svn:eol-style set to native
File size: 6.9 KB
RevLine 
[5]1# -*- coding: utf-8 -*-
2#
[1077]3# Copyright (C) 2006-2009 Edgewall Software
[22]4# Copyright (C) 2006 Matthew Good
[5]5# All rights reserved.
6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution. The terms
[287]9# are also available at http://genshi.edgewall.org/wiki/License.
[5]10#
11# This software consists of voluntary contributions made by many
12# individuals. For the exact contribution history, see the revision
[287]13# history and logs, available at http://genshi.edgewall.org/log/.
[5]14
[30]15"""Basic support for the template engine plugin API used by TurboGears and
16CherryPy/Buffet.
17"""
18
[358]19from genshi.input import ET, HTML, XML
[359]20from genshi.output import DocType
[534]21from genshi.template.base import Template
[414]22from genshi.template.loader import TemplateLoader
23from genshi.template.markup import MarkupTemplate
[706]24from genshi.template.text import TextTemplate, NewTextTemplate
[5]25
[527]26__all__ = ['ConfigurationError', 'AbstractTemplateEnginePlugin',
27           'MarkupTemplateEnginePlugin', 'TextTemplateEnginePlugin']
[517]28__docformat__ = 'restructuredtext en'
[5]29
[433]30
[562]31class ConfigurationError(ValueError):
[359]32    """Exception raised when invalid plugin options are encountered."""
33
34
[330]35class AbstractTemplateEnginePlugin(object):
[30]36    """Implementation of the plugin API."""
[5]37
[330]38    template_class = None
39    extension = None
40
[5]41    def __init__(self, extra_vars_func=None, options=None):
[359]42        self.get_extra_vars = extra_vars_func
[5]43        if options is None:
44            options = {}
45        self.options = options
46
[1160]47        self.default_encoding = options.get('genshi.default_encoding', None)
[433]48        auto_reload = options.get('genshi.auto_reload', '1')
49        if isinstance(auto_reload, basestring):
50            auto_reload = auto_reload.lower() in ('1', 'on', 'yes', 'true')
[1077]51        search_path = [p for p in
52                       options.get('genshi.search_path', '').split(':') if p]
[508]53        self.use_package_naming = not search_path
[359]54        try:
55            max_cache_size = int(options.get('genshi.max_cache_size', 25))
56        except ValueError:
57            raise ConfigurationError('Invalid value for max_cache_size: "%s"' %
[433]58                                     options.get('genshi.max_cache_size'))
[359]59
[646]60        loader_callback = options.get('genshi.loader_callback', None)
[971]61        if loader_callback and not hasattr(loader_callback, '__call__'):
[646]62            raise ConfigurationError('loader callback must be a function')
63
[722]64        lookup_errors = options.get('genshi.lookup_errors', 'strict')
[534]65        if lookup_errors not in ('lenient', 'strict'):
66            raise ConfigurationError('Unknown lookup errors mode "%s"' %
67                                     lookup_errors)
68
[654]69        try:
70            allow_exec = bool(options.get('genshi.allow_exec', True))
71        except ValueError:
72            raise ConfigurationError('Invalid value for allow_exec "%s"' %
73                                     options.get('genshi.allow_exec'))
74
[1082]75        self.loader = TemplateLoader([p for p in search_path if p],
[370]76                                     auto_reload=auto_reload,
[457]77                                     max_cache_size=max_cache_size,
[534]78                                     default_class=self.template_class,
[646]79                                     variable_lookup=lookup_errors,
[654]80                                     allow_exec=allow_exec,
[646]81                                     callback=loader_callback)
[359]82
[286]83    def load_template(self, templatename, template_string=None):
84        """Find a template specified in python 'dot' notation, or load one from
85        a string.
86        """
87        if template_string is not None:
[330]88            return self.template_class(template_string)
[286]89
[508]90        if self.use_package_naming:
91            divider = templatename.rfind('.')
92            if divider >= 0:
[895]93                from pkg_resources import resource_filename
[508]94                package = templatename[:divider]
95                basename = templatename[divider + 1:] + self.extension
96                templatename = resource_filename(package, basename)
[5]97
[457]98        return self.loader.load(templatename)
[5]99
[767]100    def _get_render_options(self, format=None, fragment=False):
[359]101        if format is None:
102            format = self.default_format
103        kwargs = {'method': format}
104        if self.default_encoding:
105            kwargs['encoding'] = self.default_encoding
106        return kwargs
107
108    def render(self, info, format=None, fragment=False, template=None):
[22]109        """Render the template to a string using the provided info."""
[767]110        kwargs = self._get_render_options(format=format, fragment=fragment)
[359]111        return self.transform(info, template).render(**kwargs)
[5]112
113    def transform(self, info, template):
[22]114        """Render the output to an event stream."""
[5]115        if not isinstance(template, Template):
116            template = self.load_template(template)
[520]117        return template.generate(**info)
[5]118
[242]119
[330]120class MarkupTemplateEnginePlugin(AbstractTemplateEnginePlugin):
121    """Implementation of the plugin API for markup templates."""
122
123    template_class = MarkupTemplate
124    extension = '.html'
125
[359]126    def __init__(self, extra_vars_func=None, options=None):
127        AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
128
[562]129        default_doctype = self.options.get('genshi.default_doctype')
130        if default_doctype:
131            doctype = DocType.get(default_doctype)
132            if doctype is None:
133                raise ConfigurationError('Unknown doctype %r' % default_doctype)
134            self.default_doctype = doctype
135        else:
136            self.default_doctype = None
[359]137
[562]138        format = self.options.get('genshi.default_format', 'html').lower()
[359]139        if format not in ('html', 'xhtml', 'xml', 'text'):
[562]140            raise ConfigurationError('Unknown output format %r' % format)
[359]141        self.default_format = format
142
[767]143    def _get_render_options(self, format=None, fragment=False):
[359]144        kwargs = super(MarkupTemplateEnginePlugin,
[767]145                       self)._get_render_options(format, fragment)
146        if self.default_doctype and not fragment:
[359]147            kwargs['doctype'] = self.default_doctype
148        return kwargs
149
[330]150    def transform(self, info, template):
151        """Render the output to an event stream."""
152        data = {'ET': ET, 'HTML': HTML, 'XML': XML}
153        if self.get_extra_vars:
154            data.update(self.get_extra_vars())
155        data.update(info)
156        return super(MarkupTemplateEnginePlugin, self).transform(data, template)
157
158
159class TextTemplateEnginePlugin(AbstractTemplateEnginePlugin):
160    """Implementation of the plugin API for text templates."""
161
162    template_class = TextTemplate
163    extension = '.txt'
[359]164    default_format = 'text'
[706]165
166    def __init__(self, extra_vars_func=None, options=None):
167        if options is None:
168            options = {}
169
170        new_syntax = options.get('genshi.new_text_syntax')
171        if isinstance(new_syntax, basestring):
172            new_syntax = new_syntax.lower() in ('1', 'on', 'yes', 'true')
173        if new_syntax:
174            self.template_class = NewTextTemplate
175
176        AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
Note: See TracBrowser for help on using the repository browser.