Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: third_party/pystache/src/renderer.py

Issue 2962783004: Adding pystache to third_party (Closed)
Patch Set: Merge branch 'master' into mustache Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/pystache/src/renderengine.py ('k') | third_party/pystache/src/specloader.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # coding: utf-8
2
3 """
4 This module provides a Renderer class to render templates.
5
6 """
7
8 import sys
9
10 from pystache import defaults
11 from pystache.common import TemplateNotFoundError, MissingTags, is_string
12 from pystache.context import ContextStack, KeyNotFoundError
13 from pystache.loader import Loader
14 from pystache.parsed import ParsedTemplate
15 from pystache.renderengine import context_get, RenderEngine
16 from pystache.specloader import SpecLoader
17 from pystache.template_spec import TemplateSpec
18
19
20 class Renderer(object):
21
22 """
23 A class for rendering mustache templates.
24
25 This class supports several rendering options which are described in
26 the constructor's docstring. Other behavior can be customized by
27 subclassing this class.
28
29 For example, one can pass a string-string dictionary to the constructor
30 to bypass loading partials from the file system:
31
32 >>> partials = {'partial': 'Hello, {{thing}}!'}
33 >>> renderer = Renderer(partials=partials)
34 >>> # We apply print to make the test work in Python 3 after 2to3.
35 >>> print renderer.render('{{>partial}}', {'thing': 'world'})
36 Hello, world!
37
38 To customize string coercion (e.g. to render False values as ''), one can
39 subclass this class. For example:
40
41 class MyRenderer(Renderer):
42 def str_coerce(self, val):
43 if not val:
44 return ''
45 else:
46 return str(val)
47
48 """
49
50 def __init__(self, file_encoding=None, string_encoding=None,
51 decode_errors=None, search_dirs=None, file_extension=None,
52 escape=None, partials=None, missing_tags=None):
53 """
54 Construct an instance.
55
56 Arguments:
57
58 file_encoding: the name of the encoding to use by default when
59 reading template files. All templates are converted to unicode
60 prior to parsing. Defaults to the package default.
61
62 string_encoding: the name of the encoding to use when converting
63 to unicode any byte strings (type str in Python 2) encountered
64 during the rendering process. This name will be passed as the
65 encoding argument to the built-in function unicode().
66 Defaults to the package default.
67
68 decode_errors: the string to pass as the errors argument to the
69 built-in function unicode() when converting byte strings to
70 unicode. Defaults to the package default.
71
72 search_dirs: the list of directories in which to search when
73 loading a template by name or file name. If given a string,
74 the method interprets the string as a single directory.
75 Defaults to the package default.
76
77 file_extension: the template file extension. Pass False for no
78 extension (i.e. to use extensionless template files).
79 Defaults to the package default.
80
81 partials: an object (e.g. a dictionary) for custom partial loading
82 during the rendering process.
83 The object should have a get() method that accepts a string
84 and returns the corresponding template as a string, preferably
85 as a unicode string. If there is no template with that name,
86 the get() method should either return None (as dict.get() does)
87 or raise an exception.
88 If this argument is None, the rendering process will use
89 the normal procedure of locating and reading templates from
90 the file system -- using relevant instance attributes like
91 search_dirs, file_encoding, etc.
92
93 escape: the function used to escape variable tag values when
94 rendering a template. The function should accept a unicode
95 string (or subclass of unicode) and return an escaped string
96 that is again unicode (or a subclass of unicode).
97 This function need not handle strings of type `str` because
98 this class will only pass it unicode strings. The constructor
99 assigns this function to the constructed instance's escape()
100 method.
101 To disable escaping entirely, one can pass `lambda u: u`
102 as the escape function, for example. One may also wish to
103 consider using markupsafe's escape function: markupsafe.escape().
104 This argument defaults to the package default.
105
106 missing_tags: a string specifying how to handle missing tags.
107 If 'strict', an error is raised on a missing tag. If 'ignore',
108 the value of the tag is the empty string. Defaults to the
109 package default.
110
111 """
112 if decode_errors is None:
113 decode_errors = defaults.DECODE_ERRORS
114
115 if escape is None:
116 escape = defaults.TAG_ESCAPE
117
118 if file_encoding is None:
119 file_encoding = defaults.FILE_ENCODING
120
121 if file_extension is None:
122 file_extension = defaults.TEMPLATE_EXTENSION
123
124 if missing_tags is None:
125 missing_tags = defaults.MISSING_TAGS
126
127 if search_dirs is None:
128 search_dirs = defaults.SEARCH_DIRS
129
130 if string_encoding is None:
131 string_encoding = defaults.STRING_ENCODING
132
133 if isinstance(search_dirs, basestring):
134 search_dirs = [search_dirs]
135
136 self._context = None
137 self.decode_errors = decode_errors
138 self.escape = escape
139 self.file_encoding = file_encoding
140 self.file_extension = file_extension
141 self.missing_tags = missing_tags
142 self.partials = partials
143 self.search_dirs = search_dirs
144 self.string_encoding = string_encoding
145
146 # This is an experimental way of giving views access to the current context.
147 # TODO: consider another approach of not giving access via a property,
148 # but instead letting the caller pass the initial context to the
149 # main render() method by reference. This approach would probably
150 # be less likely to be misused.
151 @property
152 def context(self):
153 """
154 Return the current rendering context [experimental].
155
156 """
157 return self._context
158
159 # We could not choose str() as the name because 2to3 renames the unicode()
160 # method of this class to str().
161 def str_coerce(self, val):
162 """
163 Coerce a non-string value to a string.
164
165 This method is called whenever a non-string is encountered during the
166 rendering process when a string is needed (e.g. if a context value
167 for string interpolation is not a string). To customize string
168 coercion, you can override this method.
169
170 """
171 return str(val)
172
173 def _to_unicode_soft(self, s):
174 """
175 Convert a basestring to unicode, preserving any unicode subclass.
176
177 """
178 # We type-check to avoid "TypeError: decoding Unicode is not supported".
179 # We avoid the Python ternary operator for Python 2.4 support.
180 if isinstance(s, unicode):
181 return s
182 return self.unicode(s)
183
184 def _to_unicode_hard(self, s):
185 """
186 Convert a basestring to a string with type unicode (not subclass).
187
188 """
189 return unicode(self._to_unicode_soft(s))
190
191 def _escape_to_unicode(self, s):
192 """
193 Convert a basestring to unicode (preserving any unicode subclass), and e scape it.
194
195 Returns a unicode string (not subclass).
196
197 """
198 return unicode(self.escape(self._to_unicode_soft(s)))
199
200 def unicode(self, b, encoding=None):
201 """
202 Convert a byte string to unicode, using string_encoding and decode_error s.
203
204 Arguments:
205
206 b: a byte string.
207
208 encoding: the name of an encoding. Defaults to the string_encoding
209 attribute for this instance.
210
211 Raises:
212
213 TypeError: Because this method calls Python's built-in unicode()
214 function, this method raises the following exception if the
215 given string is already unicode:
216
217 TypeError: decoding Unicode is not supported
218
219 """
220 if encoding is None:
221 encoding = self.string_encoding
222
223 # TODO: Wrap UnicodeDecodeErrors with a message about setting
224 # the string_encoding and decode_errors attributes.
225 return unicode(b, encoding, self.decode_errors)
226
227 def _make_loader(self):
228 """
229 Create a Loader instance using current attributes.
230
231 """
232 return Loader(file_encoding=self.file_encoding, extension=self.file_exte nsion,
233 to_unicode=self.unicode, search_dirs=self.search_dirs)
234
235 def _make_load_template(self):
236 """
237 Return a function that loads a template by name.
238
239 """
240 loader = self._make_loader()
241
242 def load_template(template_name):
243 return loader.load_name(template_name)
244
245 return load_template
246
247 def _make_load_partial(self):
248 """
249 Return a function that loads a partial by name.
250
251 """
252 if self.partials is None:
253 return self._make_load_template()
254
255 # Otherwise, create a function from the custom partial loader.
256 partials = self.partials
257
258 def load_partial(name):
259 # TODO: consider using EAFP here instead.
260 # http://docs.python.org/glossary.html#term-eafp
261 # This would mean requiring that the custom partial loader
262 # raise a KeyError on name not found.
263 template = partials.get(name)
264 if template is None:
265 raise TemplateNotFoundError("Name %s not found in partials: %s" %
266 (repr(name), type(partials)))
267
268 # RenderEngine requires that the return value be unicode.
269 return self._to_unicode_hard(template)
270
271 return load_partial
272
273 def _is_missing_tags_strict(self):
274 """
275 Return whether missing_tags is set to strict.
276
277 """
278 val = self.missing_tags
279
280 if val == MissingTags.strict:
281 return True
282 elif val == MissingTags.ignore:
283 return False
284
285 raise Exception("Unsupported 'missing_tags' value: %s" % repr(val))
286
287 def _make_resolve_partial(self):
288 """
289 Return the resolve_partial function to pass to RenderEngine.__init__().
290
291 """
292 load_partial = self._make_load_partial()
293
294 if self._is_missing_tags_strict():
295 return load_partial
296 # Otherwise, ignore missing tags.
297
298 def resolve_partial(name):
299 try:
300 return load_partial(name)
301 except TemplateNotFoundError:
302 return u''
303
304 return resolve_partial
305
306 def _make_resolve_context(self):
307 """
308 Return the resolve_context function to pass to RenderEngine.__init__().
309
310 """
311 if self._is_missing_tags_strict():
312 return context_get
313 # Otherwise, ignore missing tags.
314
315 def resolve_context(stack, name):
316 try:
317 return context_get(stack, name)
318 except KeyNotFoundError:
319 return u''
320
321 return resolve_context
322
323 def _make_render_engine(self):
324 """
325 Return a RenderEngine instance for rendering.
326
327 """
328 resolve_context = self._make_resolve_context()
329 resolve_partial = self._make_resolve_partial()
330
331 engine = RenderEngine(literal=self._to_unicode_hard,
332 escape=self._escape_to_unicode,
333 resolve_context=resolve_context,
334 resolve_partial=resolve_partial,
335 to_str=self.str_coerce)
336 return engine
337
338 # TODO: add unit tests for this method.
339 def load_template(self, template_name):
340 """
341 Load a template by name from the file system.
342
343 """
344 load_template = self._make_load_template()
345 return load_template(template_name)
346
347 def _render_object(self, obj, *context, **kwargs):
348 """
349 Render the template associated with the given object.
350
351 """
352 loader = self._make_loader()
353
354 # TODO: consider an approach that does not require using an if
355 # block here. For example, perhaps this class's loader can be
356 # a SpecLoader in all cases, and the SpecLoader instance can
357 # check the object's type. Or perhaps Loader and SpecLoader
358 # can be refactored to implement the same interface.
359 if isinstance(obj, TemplateSpec):
360 loader = SpecLoader(loader)
361 template = loader.load(obj)
362 else:
363 template = loader.load_object(obj)
364
365 context = [obj] + list(context)
366
367 return self._render_string(template, *context, **kwargs)
368
369 def render_name(self, template_name, *context, **kwargs):
370 """
371 Render the template with the given name using the given context.
372
373 See the render() docstring for more information.
374
375 """
376 loader = self._make_loader()
377 template = loader.load_name(template_name)
378 return self._render_string(template, *context, **kwargs)
379
380 def render_path(self, template_path, *context, **kwargs):
381 """
382 Render the template at the given path using the given context.
383
384 Read the render() docstring for more information.
385
386 """
387 loader = self._make_loader()
388 template = loader.read(template_path)
389
390 return self._render_string(template, *context, **kwargs)
391
392 def _render_string(self, template, *context, **kwargs):
393 """
394 Render the given template string using the given context.
395
396 """
397 # RenderEngine.render() requires that the template string be unicode.
398 template = self._to_unicode_hard(template)
399
400 render_func = lambda engine, stack: engine.render(template, stack)
401
402 return self._render_final(render_func, *context, **kwargs)
403
404 # All calls to render() should end here because it prepares the
405 # context stack correctly.
406 def _render_final(self, render_func, *context, **kwargs):
407 """
408 Arguments:
409
410 render_func: a function that accepts a RenderEngine and ContextStack
411 instance and returns a template rendering as a unicode string.
412
413 """
414 stack = ContextStack.create(*context, **kwargs)
415 self._context = stack
416
417 engine = self._make_render_engine()
418
419 return render_func(engine, stack)
420
421 def render(self, template, *context, **kwargs):
422 """
423 Render the given template string, view template, or parsed template.
424
425 Returns a unicode string.
426
427 Prior to rendering, this method will convert a template that is a
428 byte string (type str in Python 2) to unicode using the string_encoding
429 and decode_errors attributes. See the constructor docstring for
430 more information.
431
432 Arguments:
433
434 template: a template string that is unicode or a byte string,
435 a ParsedTemplate instance, or another object instance. In the
436 final case, the function first looks for the template associated
437 to the object by calling this class's get_associated_template()
438 method. The rendering process also uses the passed object as
439 the first element of the context stack when rendering.
440
441 *context: zero or more dictionaries, ContextStack instances, or object s
442 with which to populate the initial context stack. None
443 arguments are skipped. Items in the *context list are added to
444 the context stack in order so that later items in the argument
445 list take precedence over earlier items.
446
447 **kwargs: additional key-value data to add to the context stack.
448 As these arguments appear after all items in the *context list,
449 in the case of key conflicts these values take precedence over
450 all items in the *context list.
451
452 """
453 if is_string(template):
454 return self._render_string(template, *context, **kwargs)
455 if isinstance(template, ParsedTemplate):
456 render_func = lambda engine, stack: template.render(engine, stack)
457 return self._render_final(render_func, *context, **kwargs)
458 # Otherwise, we assume the template is an object.
459
460 return self._render_object(template, *context, **kwargs)
OLDNEW
« no previous file with comments | « third_party/pystache/src/renderengine.py ('k') | third_party/pystache/src/specloader.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698