OLD | NEW |
(Empty) | |
| 1 # coding: utf-8 |
| 2 |
| 3 """ |
| 4 Exposes a ContextStack class. |
| 5 |
| 6 The Mustache spec makes a special distinction between two types of context |
| 7 stack elements: hashes and objects. For the purposes of interpreting the |
| 8 spec, we define these categories mutually exclusively as follows: |
| 9 |
| 10 (1) Hash: an item whose type is a subclass of dict. |
| 11 |
| 12 (2) Object: an item that is neither a hash nor an instance of a |
| 13 built-in type. |
| 14 |
| 15 """ |
| 16 |
| 17 from pystache.common import PystacheError |
| 18 |
| 19 |
| 20 # This equals '__builtin__' in Python 2 and 'builtins' in Python 3. |
| 21 _BUILTIN_MODULE = type(0).__module__ |
| 22 |
| 23 |
| 24 # We use this private global variable as a return value to represent a key |
| 25 # not being found on lookup. This lets us distinguish between the case |
| 26 # of a key's value being None with the case of a key not being found -- |
| 27 # without having to rely on exceptions (e.g. KeyError) for flow control. |
| 28 # |
| 29 # TODO: eliminate the need for a private global variable, e.g. by using the |
| 30 # preferred Python approach of "easier to ask for forgiveness than permission"
: |
| 31 # http://docs.python.org/glossary.html#term-eafp |
| 32 class NotFound(object): |
| 33 pass |
| 34 _NOT_FOUND = NotFound() |
| 35 |
| 36 |
| 37 def _get_value(context, key): |
| 38 """ |
| 39 Retrieve a key's value from a context item. |
| 40 |
| 41 Returns _NOT_FOUND if the key does not exist. |
| 42 |
| 43 The ContextStack.get() docstring documents this function's intended behavior
. |
| 44 |
| 45 """ |
| 46 if isinstance(context, dict): |
| 47 # Then we consider the argument a "hash" for the purposes of the spec. |
| 48 # |
| 49 # We do a membership test to avoid using exceptions for flow control |
| 50 # (e.g. catching KeyError). |
| 51 if key in context: |
| 52 return context[key] |
| 53 elif type(context).__module__ != _BUILTIN_MODULE: |
| 54 # Then we consider the argument an "object" for the purposes of |
| 55 # the spec. |
| 56 # |
| 57 # The elif test above lets us avoid treating instances of built-in |
| 58 # types like integers and strings as objects (cf. issue #81). |
| 59 # Instances of user-defined classes on the other hand, for example, |
| 60 # are considered objects by the test above. |
| 61 try: |
| 62 attr = getattr(context, key) |
| 63 except AttributeError: |
| 64 # TODO: distinguish the case of the attribute not existing from |
| 65 # an AttributeError being raised by the call to the attribute. |
| 66 # See the following issue for implementation ideas: |
| 67 # http://bugs.python.org/issue7559 |
| 68 pass |
| 69 else: |
| 70 # TODO: consider using EAFP here instead. |
| 71 # http://docs.python.org/glossary.html#term-eafp |
| 72 if callable(attr): |
| 73 return attr() |
| 74 return attr |
| 75 |
| 76 return _NOT_FOUND |
| 77 |
| 78 |
| 79 class KeyNotFoundError(PystacheError): |
| 80 |
| 81 """ |
| 82 An exception raised when a key is not found in a context stack. |
| 83 |
| 84 """ |
| 85 |
| 86 def __init__(self, key, details): |
| 87 self.key = key |
| 88 self.details = details |
| 89 |
| 90 def __str__(self): |
| 91 return "Key %s not found: %s" % (repr(self.key), self.details) |
| 92 |
| 93 |
| 94 class ContextStack(object): |
| 95 |
| 96 """ |
| 97 Provides dictionary-like access to a stack of zero or more items. |
| 98 |
| 99 Instances of this class are meant to act as the rendering context |
| 100 when rendering Mustache templates in accordance with mustache(5) |
| 101 and the Mustache spec. |
| 102 |
| 103 Instances encapsulate a private stack of hashes, objects, and built-in |
| 104 type instances. Querying the stack for the value of a key queries |
| 105 the items in the stack in order from last-added objects to first |
| 106 (last in, first out). |
| 107 |
| 108 Caution: this class does not currently support recursive nesting in |
| 109 that items in the stack cannot themselves be ContextStack instances. |
| 110 |
| 111 See the docstrings of the methods of this class for more details. |
| 112 |
| 113 """ |
| 114 |
| 115 # We reserve keyword arguments for future options (e.g. a "strict=True" |
| 116 # option for enabling a strict mode). |
| 117 def __init__(self, *items): |
| 118 """ |
| 119 Construct an instance, and initialize the private stack. |
| 120 |
| 121 The *items arguments are the items with which to populate the |
| 122 initial stack. Items in the argument list are added to the |
| 123 stack in order so that, in particular, items at the end of |
| 124 the argument list are queried first when querying the stack. |
| 125 |
| 126 Caution: items should not themselves be ContextStack instances, as |
| 127 recursive nesting does not behave as one might expect. |
| 128 |
| 129 """ |
| 130 self._stack = list(items) |
| 131 |
| 132 def __repr__(self): |
| 133 """ |
| 134 Return a string representation of the instance. |
| 135 |
| 136 For example-- |
| 137 |
| 138 >>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123}) |
| 139 >>> repr(context) |
| 140 "ContextStack({'alpha': 'abc'}, {'numeric': 123})" |
| 141 |
| 142 """ |
| 143 return "%s%s" % (self.__class__.__name__, tuple(self._stack)) |
| 144 |
| 145 @staticmethod |
| 146 def create(*context, **kwargs): |
| 147 """ |
| 148 Build a ContextStack instance from a sequence of context-like items. |
| 149 |
| 150 This factory-style method is more general than the ContextStack class's |
| 151 constructor in that, unlike the constructor, the argument list |
| 152 can itself contain ContextStack instances. |
| 153 |
| 154 Here is an example illustrating various aspects of this method: |
| 155 |
| 156 >>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'} |
| 157 >>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'}) |
| 158 >>> |
| 159 >>> context = ContextStack.create(obj1, None, obj2, mineral='gold') |
| 160 >>> |
| 161 >>> context.get('animal') |
| 162 'cat' |
| 163 >>> context.get('vegetable') |
| 164 'spinach' |
| 165 >>> context.get('mineral') |
| 166 'gold' |
| 167 |
| 168 Arguments: |
| 169 |
| 170 *context: zero or more dictionaries, ContextStack instances, or object
s |
| 171 with which to populate the initial context stack. None |
| 172 arguments will be skipped. Items in the *context list are |
| 173 added to the stack in order so that later items in the argument |
| 174 list take precedence over earlier items. This behavior is the |
| 175 same as the constructor's. |
| 176 |
| 177 **kwargs: additional key-value data to add to the context stack. |
| 178 As these arguments appear after all items in the *context list, |
| 179 in the case of key conflicts these values take precedence over |
| 180 all items in the *context list. This behavior is the same as |
| 181 the constructor's. |
| 182 |
| 183 """ |
| 184 items = context |
| 185 |
| 186 context = ContextStack() |
| 187 |
| 188 for item in items: |
| 189 if item is None: |
| 190 continue |
| 191 if isinstance(item, ContextStack): |
| 192 context._stack.extend(item._stack) |
| 193 else: |
| 194 context.push(item) |
| 195 |
| 196 if kwargs: |
| 197 context.push(kwargs) |
| 198 |
| 199 return context |
| 200 |
| 201 # TODO: add more unit tests for this. |
| 202 # TODO: update the docstring for dotted names. |
| 203 def get(self, name): |
| 204 """ |
| 205 Resolve a dotted name against the current context stack. |
| 206 |
| 207 This function follows the rules outlined in the section of the |
| 208 spec regarding tag interpolation. This function returns the value |
| 209 as is and does not coerce the return value to a string. |
| 210 |
| 211 Arguments: |
| 212 |
| 213 name: a dotted or non-dotted name. |
| 214 |
| 215 default: the value to return if name resolution fails at any point. |
| 216 Defaults to the empty string per the Mustache spec. |
| 217 |
| 218 This method queries items in the stack in order from last-added |
| 219 objects to first (last in, first out). The value returned is |
| 220 the value of the key in the first item that contains the key. |
| 221 If the key is not found in any item in the stack, then the default |
| 222 value is returned. The default value defaults to None. |
| 223 |
| 224 In accordance with the spec, this method queries items in the |
| 225 stack for a key differently depending on whether the item is a |
| 226 hash, object, or neither (as defined in the module docstring): |
| 227 |
| 228 (1) Hash: if the item is a hash, then the key's value is the |
| 229 dictionary value of the key. If the dictionary doesn't contain |
| 230 the key, then the key is considered not found. |
| 231 |
| 232 (2) Object: if the item is an an object, then the method looks for |
| 233 an attribute with the same name as the key. If an attribute |
| 234 with that name exists, the value of the attribute is returned. |
| 235 If the attribute is callable, however (i.e. if the attribute |
| 236 is a method), then the attribute is called with no arguments |
| 237 and that value is returned. If there is no attribute with |
| 238 the same name as the key, then the key is considered not found. |
| 239 |
| 240 (3) Neither: if the item is neither a hash nor an object, then |
| 241 the key is considered not found. |
| 242 |
| 243 *Caution*: |
| 244 |
| 245 Callables are handled differently depending on whether they are |
| 246 dictionary values, as in (1) above, or attributes, as in (2). |
| 247 The former are returned as-is, while the latter are first |
| 248 called and that value returned. |
| 249 |
| 250 Here is an example to illustrate: |
| 251 |
| 252 >>> def greet(): |
| 253 ... return "Hi Bob!" |
| 254 >>> |
| 255 >>> class Greeter(object): |
| 256 ... greet = None |
| 257 >>> |
| 258 >>> dct = {'greet': greet} |
| 259 >>> obj = Greeter() |
| 260 >>> obj.greet = greet |
| 261 >>> |
| 262 >>> dct['greet'] is obj.greet |
| 263 True |
| 264 >>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS |
| 265 <function greet at 0x...> |
| 266 >>> ContextStack(obj).get('greet') |
| 267 'Hi Bob!' |
| 268 |
| 269 TODO: explain the rationale for this difference in treatment. |
| 270 |
| 271 """ |
| 272 if name == '.': |
| 273 try: |
| 274 return self.top() |
| 275 except IndexError: |
| 276 raise KeyNotFoundError(".", "empty context stack") |
| 277 |
| 278 parts = name.split('.') |
| 279 |
| 280 try: |
| 281 result = self._get_simple(parts[0]) |
| 282 except KeyNotFoundError: |
| 283 raise KeyNotFoundError(name, "first part") |
| 284 |
| 285 for part in parts[1:]: |
| 286 # The full context stack is not used to resolve the remaining parts. |
| 287 # From the spec-- |
| 288 # |
| 289 # 5) If any name parts were retained in step 1, each should be |
| 290 # resolved against a context stack containing only the result |
| 291 # from the former resolution. If any part fails resolution, the |
| 292 # result should be considered falsey, and should interpolate as |
| 293 # the empty string. |
| 294 # |
| 295 # TODO: make sure we have a test case for the above point. |
| 296 result = _get_value(result, part) |
| 297 # TODO: consider using EAFP here instead. |
| 298 # http://docs.python.org/glossary.html#term-eafp |
| 299 if result is _NOT_FOUND: |
| 300 raise KeyNotFoundError(name, "missing %s" % repr(part)) |
| 301 |
| 302 return result |
| 303 |
| 304 def _get_simple(self, name): |
| 305 """ |
| 306 Query the stack for a non-dotted name. |
| 307 |
| 308 """ |
| 309 for item in reversed(self._stack): |
| 310 result = _get_value(item, name) |
| 311 if result is not _NOT_FOUND: |
| 312 return result |
| 313 |
| 314 raise KeyNotFoundError(name, "part missing") |
| 315 |
| 316 def push(self, item): |
| 317 """ |
| 318 Push an item onto the stack. |
| 319 |
| 320 """ |
| 321 self._stack.append(item) |
| 322 |
| 323 def pop(self): |
| 324 """ |
| 325 Pop an item off of the stack, and return it. |
| 326 |
| 327 """ |
| 328 return self._stack.pop() |
| 329 |
| 330 def top(self): |
| 331 """ |
| 332 Return the item last added to the stack. |
| 333 |
| 334 """ |
| 335 return self._stack[-1] |
| 336 |
| 337 def copy(self): |
| 338 """ |
| 339 Return a copy of this instance. |
| 340 |
| 341 """ |
| 342 return ContextStack(*self._stack) |
OLD | NEW |