OLD | NEW |
(Empty) | |
| 1 # coding: utf-8 |
| 2 |
| 3 """ |
| 4 Defines a class responsible for rendering logic. |
| 5 |
| 6 """ |
| 7 |
| 8 import re |
| 9 |
| 10 from pystache.common import is_string |
| 11 from pystache.parser import parse |
| 12 |
| 13 |
| 14 def context_get(stack, name): |
| 15 """ |
| 16 Find and return a name from a ContextStack instance. |
| 17 |
| 18 """ |
| 19 return stack.get(name) |
| 20 |
| 21 |
| 22 class RenderEngine(object): |
| 23 |
| 24 """ |
| 25 Provides a render() method. |
| 26 |
| 27 This class is meant only for internal use. |
| 28 |
| 29 As a rule, the code in this class operates on unicode strings where |
| 30 possible rather than, say, strings of type str or markupsafe.Markup. |
| 31 This means that strings obtained from "external" sources like partials |
| 32 and variable tag values are immediately converted to unicode (or |
| 33 escaped and converted to unicode) before being operated on further. |
| 34 This makes maintaining, reasoning about, and testing the correctness |
| 35 of the code much simpler. In particular, it keeps the implementation |
| 36 of this class independent of the API details of one (or possibly more) |
| 37 unicode subclasses (e.g. markupsafe.Markup). |
| 38 |
| 39 """ |
| 40 |
| 41 # TODO: it would probably be better for the constructor to accept |
| 42 # and set as an attribute a single RenderResolver instance |
| 43 # that encapsulates the customizable aspects of converting |
| 44 # strings and resolving partials and names from context. |
| 45 def __init__(self, literal=None, escape=None, resolve_context=None, |
| 46 resolve_partial=None, to_str=None): |
| 47 """ |
| 48 Arguments: |
| 49 |
| 50 literal: the function used to convert unescaped variable tag |
| 51 values to unicode, e.g. the value corresponding to a tag |
| 52 "{{{name}}}". The function should accept a string of type |
| 53 str or unicode (or a subclass) and return a string of type |
| 54 unicode (but not a proper subclass of unicode). |
| 55 This class will only pass basestring instances to this |
| 56 function. For example, it will call str() on integer variable |
| 57 values prior to passing them to this function. |
| 58 |
| 59 escape: the function used to escape and convert variable tag |
| 60 values to unicode, e.g. the value corresponding to a tag |
| 61 "{{name}}". The function should obey the same properties |
| 62 described above for the "literal" function argument. |
| 63 This function should take care to convert any str |
| 64 arguments to unicode just as the literal function should, as |
| 65 this class will not pass tag values to literal prior to passing |
| 66 them to this function. This allows for more flexibility, |
| 67 for example using a custom escape function that handles |
| 68 incoming strings of type markupsafe.Markup differently |
| 69 from plain unicode strings. |
| 70 |
| 71 resolve_context: the function to call to resolve a name against |
| 72 a context stack. The function should accept two positional |
| 73 arguments: a ContextStack instance and a name to resolve. |
| 74 |
| 75 resolve_partial: the function to call when loading a partial. |
| 76 The function should accept a template name string and return a |
| 77 template string of type unicode (not a subclass). |
| 78 |
| 79 to_str: a function that accepts an object and returns a string (e.g. |
| 80 the built-in function str). This function is used for string |
| 81 coercion whenever a string is required (e.g. for converting None |
| 82 or 0 to a string). |
| 83 |
| 84 """ |
| 85 self.escape = escape |
| 86 self.literal = literal |
| 87 self.resolve_context = resolve_context |
| 88 self.resolve_partial = resolve_partial |
| 89 self.to_str = to_str |
| 90 |
| 91 # TODO: Rename context to stack throughout this module. |
| 92 |
| 93 # From the spec: |
| 94 # |
| 95 # When used as the data value for an Interpolation tag, the lambda |
| 96 # MUST be treatable as an arity 0 function, and invoked as such. |
| 97 # The returned value MUST be rendered against the default delimiters, |
| 98 # then interpolated in place of the lambda. |
| 99 # |
| 100 def fetch_string(self, context, name): |
| 101 """ |
| 102 Get a value from the given context as a basestring instance. |
| 103 |
| 104 """ |
| 105 val = self.resolve_context(context, name) |
| 106 |
| 107 if callable(val): |
| 108 # Return because _render_value() is already a string. |
| 109 return self._render_value(val(), context) |
| 110 |
| 111 if not is_string(val): |
| 112 return self.to_str(val) |
| 113 |
| 114 return val |
| 115 |
| 116 def fetch_section_data(self, context, name): |
| 117 """ |
| 118 Fetch the value of a section as a list. |
| 119 |
| 120 """ |
| 121 data = self.resolve_context(context, name) |
| 122 |
| 123 # From the spec: |
| 124 # |
| 125 # If the data is not of a list type, it is coerced into a list |
| 126 # as follows: if the data is truthy (e.g. `!!data == true`), |
| 127 # use a single-element list containing the data, otherwise use |
| 128 # an empty list. |
| 129 # |
| 130 if not data: |
| 131 data = [] |
| 132 else: |
| 133 # The least brittle way to determine whether something |
| 134 # supports iteration is by trying to call iter() on it: |
| 135 # |
| 136 # http://docs.python.org/library/functions.html#iter |
| 137 # |
| 138 # It is not sufficient, for example, to check whether the item |
| 139 # implements __iter__ () (the iteration protocol). There is |
| 140 # also __getitem__() (the sequence protocol). In Python 2, |
| 141 # strings do not implement __iter__(), but in Python 3 they do. |
| 142 try: |
| 143 iter(data) |
| 144 except TypeError: |
| 145 # Then the value does not support iteration. |
| 146 data = [data] |
| 147 else: |
| 148 if is_string(data) or isinstance(data, dict): |
| 149 # Do not treat strings and dicts (which are iterable) as lis
ts. |
| 150 data = [data] |
| 151 # Otherwise, treat the value as a list. |
| 152 |
| 153 return data |
| 154 |
| 155 def _render_value(self, val, context, delimiters=None): |
| 156 """ |
| 157 Render an arbitrary value. |
| 158 |
| 159 """ |
| 160 if not is_string(val): |
| 161 # In case the template is an integer, for example. |
| 162 val = self.to_str(val) |
| 163 if type(val) is not unicode: |
| 164 val = self.literal(val) |
| 165 return self.render(val, context, delimiters) |
| 166 |
| 167 def render(self, template, context_stack, delimiters=None): |
| 168 """ |
| 169 Render a unicode template string, and return as unicode. |
| 170 |
| 171 Arguments: |
| 172 |
| 173 template: a template string of type unicode (but not a proper |
| 174 subclass of unicode). |
| 175 |
| 176 context_stack: a ContextStack instance. |
| 177 |
| 178 """ |
| 179 parsed_template = parse(template, delimiters) |
| 180 |
| 181 return parsed_template.render(self, context_stack) |
OLD | NEW |