[3] | 1 | # template.py |
---|
| 2 | # Copyright (C) 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
| 3 | # |
---|
| 4 | # This module is part of Mako and is released under |
---|
| 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
| 6 | |
---|
| 7 | """provides the Template class, a facade for parsing, generating and executing template strings, |
---|
| 8 | as well as template runtime operations.""" |
---|
| 9 | |
---|
| 10 | from mako.lexer import Lexer |
---|
| 11 | from mako import codegen |
---|
| 12 | from mako import runtime, util, exceptions |
---|
| 13 | import imp, os, re, shutil, stat, sys, tempfile, time, types, weakref |
---|
| 14 | |
---|
| 15 | |
---|
| 16 | class Template(object): |
---|
| 17 | """a compiled template""" |
---|
| 18 | def __init__(self, text=None, filename=None, uri=None, format_exceptions=False, error_handler=None, |
---|
| 19 | lookup=None, output_encoding=None, encoding_errors='strict', module_directory=None, cache_type=None, |
---|
| 20 | cache_dir=None, cache_url=None, module_filename=None, input_encoding=None, disable_unicode=False, default_filters=None, |
---|
| 21 | buffer_filters=[], imports=None, preprocessor=None, cache_enabled=True): |
---|
| 22 | """construct a new Template instance using either literal template text, or a previously loaded template module |
---|
| 23 | |
---|
| 24 | text - textual template source, or None if a module is to be provided |
---|
| 25 | |
---|
| 26 | uri - the uri of this template, or some identifying string. defaults to the |
---|
| 27 | full filename given, or "memory:(hex id of this Template)" if no filename |
---|
| 28 | |
---|
| 29 | filename - filename of the source template, if any |
---|
| 30 | |
---|
| 31 | format_exceptions - catch exceptions and format them into an error display template |
---|
| 32 | """ |
---|
| 33 | |
---|
| 34 | if uri: |
---|
| 35 | self.module_id = re.sub(r'\W', "_", uri) |
---|
| 36 | self.uri = uri |
---|
| 37 | elif filename: |
---|
| 38 | self.module_id = re.sub(r'\W', "_", filename) |
---|
| 39 | self.uri = filename |
---|
| 40 | else: |
---|
| 41 | self.module_id = "memory:" + hex(id(self)) |
---|
| 42 | self.uri = self.module_id |
---|
| 43 | |
---|
| 44 | self.input_encoding = input_encoding |
---|
| 45 | self.output_encoding = output_encoding |
---|
| 46 | self.encoding_errors = encoding_errors |
---|
| 47 | self.disable_unicode = disable_unicode |
---|
| 48 | if default_filters is None: |
---|
| 49 | if self.disable_unicode: |
---|
| 50 | self.default_filters = ['str'] |
---|
| 51 | else: |
---|
| 52 | self.default_filters = ['unicode'] |
---|
| 53 | else: |
---|
| 54 | self.default_filters = default_filters |
---|
| 55 | self.buffer_filters = buffer_filters |
---|
| 56 | |
---|
| 57 | self.imports = imports |
---|
| 58 | self.preprocessor = preprocessor |
---|
| 59 | |
---|
| 60 | # if plain text, compile code in memory only |
---|
| 61 | if text is not None: |
---|
| 62 | (code, module) = _compile_text(self, text, filename) |
---|
| 63 | self._code = code |
---|
| 64 | self._source = text |
---|
| 65 | ModuleInfo(module, None, self, filename, code, text) |
---|
| 66 | elif filename is not None: |
---|
| 67 | # if template filename and a module directory, load |
---|
| 68 | # a filesystem-based module file, generating if needed |
---|
| 69 | if module_filename is not None: |
---|
| 70 | path = module_filename |
---|
| 71 | elif module_directory is not None: |
---|
| 72 | u = self.uri |
---|
| 73 | if u[0] == '/': |
---|
| 74 | u = u[1:] |
---|
| 75 | path = os.path.abspath(os.path.join(module_directory.replace('/', os.path.sep), u + ".py")) |
---|
| 76 | else: |
---|
| 77 | path = None |
---|
| 78 | if path is not None: |
---|
| 79 | util.verify_directory(os.path.dirname(path)) |
---|
| 80 | filemtime = os.stat(filename)[stat.ST_MTIME] |
---|
| 81 | if not os.path.exists(path) or os.stat(path)[stat.ST_MTIME] < filemtime: |
---|
| 82 | _compile_module_file(self, file(filename).read(), filename, path) |
---|
| 83 | module = imp.load_source(self.module_id, path, file(path)) |
---|
| 84 | del sys.modules[self.module_id] |
---|
| 85 | if module._magic_number != codegen.MAGIC_NUMBER: |
---|
| 86 | _compile_module_file(self, file(filename).read(), filename, path) |
---|
| 87 | module = imp.load_source(self.module_id, path, file(path)) |
---|
| 88 | del sys.modules[self.module_id] |
---|
| 89 | ModuleInfo(module, path, self, filename, None, None) |
---|
| 90 | else: |
---|
| 91 | # template filename and no module directory, compile code |
---|
| 92 | # in memory |
---|
| 93 | (code, module) = _compile_text(self, file(filename).read(), filename) |
---|
| 94 | self._source = None |
---|
| 95 | self._code = code |
---|
| 96 | ModuleInfo(module, None, self, filename, code, None) |
---|
| 97 | else: |
---|
| 98 | raise exceptions.RuntimeException("Template requires text or filename") |
---|
| 99 | |
---|
| 100 | self.module = module |
---|
| 101 | self.filename = filename |
---|
| 102 | self.callable_ = self.module.render_body |
---|
| 103 | self.format_exceptions = format_exceptions |
---|
| 104 | self.error_handler = error_handler |
---|
| 105 | self.lookup = lookup |
---|
| 106 | self.cache_type = cache_type |
---|
| 107 | self.cache_dir = cache_dir |
---|
| 108 | self.cache_url = cache_url |
---|
| 109 | self.cache_enabled = cache_enabled |
---|
| 110 | |
---|
| 111 | def source(self): |
---|
| 112 | """return the template source code for this Template.""" |
---|
| 113 | return _get_module_info_from_callable(self.callable_).source |
---|
| 114 | source = property(source) |
---|
| 115 | |
---|
| 116 | def code(self): |
---|
| 117 | """return the module source code for this Template""" |
---|
| 118 | return _get_module_info_from_callable(self.callable_).code |
---|
| 119 | code = property(code) |
---|
| 120 | |
---|
| 121 | def cache(self): |
---|
| 122 | return self.module._template_cache |
---|
| 123 | cache = property(cache) |
---|
| 124 | |
---|
| 125 | def render(self, *args, **data): |
---|
| 126 | """render the output of this template as a string. |
---|
| 127 | |
---|
| 128 | if the template specifies an output encoding, the string will be encoded accordingly, else the output |
---|
| 129 | is raw (raw output uses cStringIO and can't handle multibyte characters). |
---|
| 130 | a Context object is created corresponding to the given data. Arguments that are explictly |
---|
| 131 | declared by this template's internal rendering method are also pulled from the given *args, **data |
---|
| 132 | members.""" |
---|
| 133 | return runtime._render(self, self.callable_, args, data) |
---|
| 134 | |
---|
| 135 | def render_unicode(self, *args, **data): |
---|
| 136 | """render the output of this template as a unicode object.""" |
---|
| 137 | |
---|
| 138 | return runtime._render(self, self.callable_, args, data, as_unicode=True) |
---|
| 139 | |
---|
| 140 | def render_context(self, context, *args, **kwargs): |
---|
| 141 | """render this Template with the given context. |
---|
| 142 | |
---|
| 143 | the data is written to the context's buffer.""" |
---|
| 144 | if getattr(context, '_with_template', None) is None: |
---|
| 145 | context._with_template = self |
---|
| 146 | runtime._render_context(self, self.callable_, context, *args, **kwargs) |
---|
| 147 | |
---|
| 148 | def has_def(self, name): |
---|
| 149 | return hasattr(self.module, "render_%s" % name) |
---|
| 150 | |
---|
| 151 | def get_def(self, name): |
---|
| 152 | """return a def of this template as an individual Template of its own.""" |
---|
| 153 | return DefTemplate(self, getattr(self.module, "render_%s" % name)) |
---|
| 154 | |
---|
| 155 | def _get_def_callable(self, name): |
---|
| 156 | return getattr(self.module, "render_%s" % name) |
---|
| 157 | |
---|
| 158 | def last_modified(self): |
---|
| 159 | return self.module._modified_time |
---|
| 160 | last_modified = property(last_modified) |
---|
| 161 | |
---|
| 162 | class ModuleTemplate(Template): |
---|
| 163 | """A Template which is constructed given an existing Python module. |
---|
| 164 | |
---|
| 165 | e.g.:: |
---|
| 166 | |
---|
| 167 | t = Template("this is a template") |
---|
| 168 | f = file("mymodule.py", "w") |
---|
| 169 | f.write(t.code) |
---|
| 170 | f.close() |
---|
| 171 | |
---|
| 172 | import mymodule |
---|
| 173 | |
---|
| 174 | t = ModuleTemplate(mymodule) |
---|
| 175 | print t.render() |
---|
| 176 | |
---|
| 177 | """ |
---|
| 178 | |
---|
| 179 | def __init__(self, module, |
---|
| 180 | module_filename=None, |
---|
| 181 | template=None, template_filename=None, |
---|
| 182 | module_source=None, template_source=None, |
---|
| 183 | output_encoding=None, encoding_errors='strict', disable_unicode=False, format_exceptions=False, |
---|
| 184 | error_handler=None, lookup=None, cache_type=None, cache_dir=None, cache_url=None, cache_enabled=True |
---|
| 185 | ): |
---|
| 186 | self.module_id = re.sub(r'\W', "_", module._template_uri) |
---|
| 187 | self.uri = module._template_uri |
---|
| 188 | self.input_encoding = module._source_encoding |
---|
| 189 | self.output_encoding = output_encoding |
---|
| 190 | self.encoding_errors = encoding_errors |
---|
| 191 | self.disable_unicode = disable_unicode |
---|
| 192 | self.module = module |
---|
| 193 | self.filename = template_filename |
---|
| 194 | ModuleInfo(module, module_filename, self, template_filename, module_source, template_source) |
---|
| 195 | |
---|
| 196 | self.callable_ = self.module.render_body |
---|
| 197 | self.format_exceptions = format_exceptions |
---|
| 198 | self.error_handler = error_handler |
---|
| 199 | self.lookup = lookup |
---|
| 200 | self.cache_type = cache_type |
---|
| 201 | self.cache_dir = cache_dir |
---|
| 202 | self.cache_url = cache_url |
---|
| 203 | self.cache_enabled = cache_enabled |
---|
| 204 | |
---|
| 205 | class DefTemplate(Template): |
---|
| 206 | """a Template which represents a callable def in a parent template.""" |
---|
| 207 | def __init__(self, parent, callable_): |
---|
| 208 | self.parent = parent |
---|
| 209 | self.callable_ = callable_ |
---|
| 210 | self.output_encoding = parent.output_encoding |
---|
| 211 | self.module = parent.module |
---|
| 212 | self.encoding_errors = parent.encoding_errors |
---|
| 213 | self.format_exceptions = parent.format_exceptions |
---|
| 214 | self.error_handler = parent.error_handler |
---|
| 215 | self.lookup = parent.lookup |
---|
| 216 | |
---|
| 217 | def get_def(self, name): |
---|
| 218 | return self.parent.get_def(name) |
---|
| 219 | |
---|
| 220 | class ModuleInfo(object): |
---|
| 221 | """stores information about a module currently loaded into memory, |
---|
| 222 | provides reverse lookups of template source, module source code based on |
---|
| 223 | a module's identifier.""" |
---|
| 224 | _modules = weakref.WeakValueDictionary() |
---|
| 225 | |
---|
| 226 | def __init__(self, module, module_filename, template, template_filename, module_source, template_source): |
---|
| 227 | self.module = module |
---|
| 228 | self.module_filename = module_filename |
---|
| 229 | self.template_filename = template_filename |
---|
| 230 | self.module_source = module_source |
---|
| 231 | self.template_source = template_source |
---|
| 232 | self._modules[module.__name__] = template._mmarker = self |
---|
| 233 | if module_filename: |
---|
| 234 | self._modules[module_filename] = self |
---|
| 235 | def _get_code(self): |
---|
| 236 | if self.module_source is not None: |
---|
| 237 | return self.module_source |
---|
| 238 | else: |
---|
| 239 | return file(self.module_filename).read() |
---|
| 240 | code = property(_get_code) |
---|
| 241 | def _get_source(self): |
---|
| 242 | if self.template_source is not None: |
---|
| 243 | if self.module._source_encoding and not isinstance(self.template_source, unicode): |
---|
| 244 | return self.template_source.decode(self.module._source_encoding) |
---|
| 245 | else: |
---|
| 246 | return self.template_source |
---|
| 247 | else: |
---|
| 248 | if self.module._source_encoding: |
---|
| 249 | return file(self.template_filename).read().decode(self.module._source_encoding) |
---|
| 250 | else: |
---|
| 251 | return file(self.template_filename).read() |
---|
| 252 | source = property(_get_source) |
---|
| 253 | |
---|
| 254 | def _compile_text(template, text, filename): |
---|
| 255 | identifier = template.module_id |
---|
| 256 | lexer = Lexer(text, filename, disable_unicode=template.disable_unicode, input_encoding=template.input_encoding, preprocessor=template.preprocessor) |
---|
| 257 | node = lexer.parse() |
---|
| 258 | source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding, generate_unicode=not template.disable_unicode) |
---|
| 259 | #print source |
---|
| 260 | cid = identifier |
---|
| 261 | if isinstance(cid, unicode): |
---|
| 262 | cid = cid.encode() |
---|
| 263 | module = types.ModuleType(cid) |
---|
| 264 | code = compile(source, cid, 'exec') |
---|
| 265 | exec code in module.__dict__, module.__dict__ |
---|
| 266 | return (source, module) |
---|
| 267 | |
---|
| 268 | def _compile_module_file(template, text, filename, outputpath): |
---|
| 269 | identifier = template.module_id |
---|
| 270 | lexer = Lexer(text, filename, disable_unicode=template.disable_unicode, input_encoding=template.input_encoding, preprocessor=template.preprocessor) |
---|
| 271 | node = lexer.parse() |
---|
| 272 | source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding, generate_unicode=not template.disable_unicode) |
---|
| 273 | (dest, name) = tempfile.mkstemp() |
---|
| 274 | os.write(dest, source) |
---|
| 275 | os.close(dest) |
---|
| 276 | shutil.move(name, outputpath) |
---|
| 277 | |
---|
| 278 | def _get_module_info_from_callable(callable_): |
---|
| 279 | return _get_module_info(callable_.func_globals['__name__']) |
---|
| 280 | |
---|
| 281 | def _get_module_info(filename): |
---|
| 282 | return ModuleInfo._modules[filename] |
---|
| 283 | |
---|