| 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 | |
|---|