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