| Index: third_party/pystache/src/context.py
|
| diff --git a/third_party/pystache/src/context.py b/third_party/pystache/src/context.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..671591609299f1a34f00f2221a2221487f0b7f54
|
| --- /dev/null
|
| +++ b/third_party/pystache/src/context.py
|
| @@ -0,0 +1,342 @@
|
| +# coding: utf-8
|
| +
|
| +"""
|
| +Exposes a ContextStack class.
|
| +
|
| +The Mustache spec makes a special distinction between two types of context
|
| +stack elements: hashes and objects. For the purposes of interpreting the
|
| +spec, we define these categories mutually exclusively as follows:
|
| +
|
| + (1) Hash: an item whose type is a subclass of dict.
|
| +
|
| + (2) Object: an item that is neither a hash nor an instance of a
|
| + built-in type.
|
| +
|
| +"""
|
| +
|
| +from pystache.common import PystacheError
|
| +
|
| +
|
| +# This equals '__builtin__' in Python 2 and 'builtins' in Python 3.
|
| +_BUILTIN_MODULE = type(0).__module__
|
| +
|
| +
|
| +# We use this private global variable as a return value to represent a key
|
| +# not being found on lookup. This lets us distinguish between the case
|
| +# of a key's value being None with the case of a key not being found --
|
| +# without having to rely on exceptions (e.g. KeyError) for flow control.
|
| +#
|
| +# TODO: eliminate the need for a private global variable, e.g. by using the
|
| +# preferred Python approach of "easier to ask for forgiveness than permission":
|
| +# http://docs.python.org/glossary.html#term-eafp
|
| +class NotFound(object):
|
| + pass
|
| +_NOT_FOUND = NotFound()
|
| +
|
| +
|
| +def _get_value(context, key):
|
| + """
|
| + Retrieve a key's value from a context item.
|
| +
|
| + Returns _NOT_FOUND if the key does not exist.
|
| +
|
| + The ContextStack.get() docstring documents this function's intended behavior.
|
| +
|
| + """
|
| + if isinstance(context, dict):
|
| + # Then we consider the argument a "hash" for the purposes of the spec.
|
| + #
|
| + # We do a membership test to avoid using exceptions for flow control
|
| + # (e.g. catching KeyError).
|
| + if key in context:
|
| + return context[key]
|
| + elif type(context).__module__ != _BUILTIN_MODULE:
|
| + # Then we consider the argument an "object" for the purposes of
|
| + # the spec.
|
| + #
|
| + # The elif test above lets us avoid treating instances of built-in
|
| + # types like integers and strings as objects (cf. issue #81).
|
| + # Instances of user-defined classes on the other hand, for example,
|
| + # are considered objects by the test above.
|
| + try:
|
| + attr = getattr(context, key)
|
| + except AttributeError:
|
| + # TODO: distinguish the case of the attribute not existing from
|
| + # an AttributeError being raised by the call to the attribute.
|
| + # See the following issue for implementation ideas:
|
| + # http://bugs.python.org/issue7559
|
| + pass
|
| + else:
|
| + # TODO: consider using EAFP here instead.
|
| + # http://docs.python.org/glossary.html#term-eafp
|
| + if callable(attr):
|
| + return attr()
|
| + return attr
|
| +
|
| + return _NOT_FOUND
|
| +
|
| +
|
| +class KeyNotFoundError(PystacheError):
|
| +
|
| + """
|
| + An exception raised when a key is not found in a context stack.
|
| +
|
| + """
|
| +
|
| + def __init__(self, key, details):
|
| + self.key = key
|
| + self.details = details
|
| +
|
| + def __str__(self):
|
| + return "Key %s not found: %s" % (repr(self.key), self.details)
|
| +
|
| +
|
| +class ContextStack(object):
|
| +
|
| + """
|
| + Provides dictionary-like access to a stack of zero or more items.
|
| +
|
| + Instances of this class are meant to act as the rendering context
|
| + when rendering Mustache templates in accordance with mustache(5)
|
| + and the Mustache spec.
|
| +
|
| + Instances encapsulate a private stack of hashes, objects, and built-in
|
| + type instances. Querying the stack for the value of a key queries
|
| + the items in the stack in order from last-added objects to first
|
| + (last in, first out).
|
| +
|
| + Caution: this class does not currently support recursive nesting in
|
| + that items in the stack cannot themselves be ContextStack instances.
|
| +
|
| + See the docstrings of the methods of this class for more details.
|
| +
|
| + """
|
| +
|
| + # We reserve keyword arguments for future options (e.g. a "strict=True"
|
| + # option for enabling a strict mode).
|
| + def __init__(self, *items):
|
| + """
|
| + Construct an instance, and initialize the private stack.
|
| +
|
| + The *items arguments are the items with which to populate the
|
| + initial stack. Items in the argument list are added to the
|
| + stack in order so that, in particular, items at the end of
|
| + the argument list are queried first when querying the stack.
|
| +
|
| + Caution: items should not themselves be ContextStack instances, as
|
| + recursive nesting does not behave as one might expect.
|
| +
|
| + """
|
| + self._stack = list(items)
|
| +
|
| + def __repr__(self):
|
| + """
|
| + Return a string representation of the instance.
|
| +
|
| + For example--
|
| +
|
| + >>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123})
|
| + >>> repr(context)
|
| + "ContextStack({'alpha': 'abc'}, {'numeric': 123})"
|
| +
|
| + """
|
| + return "%s%s" % (self.__class__.__name__, tuple(self._stack))
|
| +
|
| + @staticmethod
|
| + def create(*context, **kwargs):
|
| + """
|
| + Build a ContextStack instance from a sequence of context-like items.
|
| +
|
| + This factory-style method is more general than the ContextStack class's
|
| + constructor in that, unlike the constructor, the argument list
|
| + can itself contain ContextStack instances.
|
| +
|
| + Here is an example illustrating various aspects of this method:
|
| +
|
| + >>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'}
|
| + >>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'})
|
| + >>>
|
| + >>> context = ContextStack.create(obj1, None, obj2, mineral='gold')
|
| + >>>
|
| + >>> context.get('animal')
|
| + 'cat'
|
| + >>> context.get('vegetable')
|
| + 'spinach'
|
| + >>> context.get('mineral')
|
| + 'gold'
|
| +
|
| + Arguments:
|
| +
|
| + *context: zero or more dictionaries, ContextStack instances, or objects
|
| + with which to populate the initial context stack. None
|
| + arguments will be skipped. Items in the *context list are
|
| + added to the stack in order so that later items in the argument
|
| + list take precedence over earlier items. This behavior is the
|
| + same as the constructor's.
|
| +
|
| + **kwargs: additional key-value data to add to the context stack.
|
| + As these arguments appear after all items in the *context list,
|
| + in the case of key conflicts these values take precedence over
|
| + all items in the *context list. This behavior is the same as
|
| + the constructor's.
|
| +
|
| + """
|
| + items = context
|
| +
|
| + context = ContextStack()
|
| +
|
| + for item in items:
|
| + if item is None:
|
| + continue
|
| + if isinstance(item, ContextStack):
|
| + context._stack.extend(item._stack)
|
| + else:
|
| + context.push(item)
|
| +
|
| + if kwargs:
|
| + context.push(kwargs)
|
| +
|
| + return context
|
| +
|
| + # TODO: add more unit tests for this.
|
| + # TODO: update the docstring for dotted names.
|
| + def get(self, name):
|
| + """
|
| + Resolve a dotted name against the current context stack.
|
| +
|
| + This function follows the rules outlined in the section of the
|
| + spec regarding tag interpolation. This function returns the value
|
| + as is and does not coerce the return value to a string.
|
| +
|
| + Arguments:
|
| +
|
| + name: a dotted or non-dotted name.
|
| +
|
| + default: the value to return if name resolution fails at any point.
|
| + Defaults to the empty string per the Mustache spec.
|
| +
|
| + This method queries items in the stack in order from last-added
|
| + objects to first (last in, first out). The value returned is
|
| + the value of the key in the first item that contains the key.
|
| + If the key is not found in any item in the stack, then the default
|
| + value is returned. The default value defaults to None.
|
| +
|
| + In accordance with the spec, this method queries items in the
|
| + stack for a key differently depending on whether the item is a
|
| + hash, object, or neither (as defined in the module docstring):
|
| +
|
| + (1) Hash: if the item is a hash, then the key's value is the
|
| + dictionary value of the key. If the dictionary doesn't contain
|
| + the key, then the key is considered not found.
|
| +
|
| + (2) Object: if the item is an an object, then the method looks for
|
| + an attribute with the same name as the key. If an attribute
|
| + with that name exists, the value of the attribute is returned.
|
| + If the attribute is callable, however (i.e. if the attribute
|
| + is a method), then the attribute is called with no arguments
|
| + and that value is returned. If there is no attribute with
|
| + the same name as the key, then the key is considered not found.
|
| +
|
| + (3) Neither: if the item is neither a hash nor an object, then
|
| + the key is considered not found.
|
| +
|
| + *Caution*:
|
| +
|
| + Callables are handled differently depending on whether they are
|
| + dictionary values, as in (1) above, or attributes, as in (2).
|
| + The former are returned as-is, while the latter are first
|
| + called and that value returned.
|
| +
|
| + Here is an example to illustrate:
|
| +
|
| + >>> def greet():
|
| + ... return "Hi Bob!"
|
| + >>>
|
| + >>> class Greeter(object):
|
| + ... greet = None
|
| + >>>
|
| + >>> dct = {'greet': greet}
|
| + >>> obj = Greeter()
|
| + >>> obj.greet = greet
|
| + >>>
|
| + >>> dct['greet'] is obj.greet
|
| + True
|
| + >>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS
|
| + <function greet at 0x...>
|
| + >>> ContextStack(obj).get('greet')
|
| + 'Hi Bob!'
|
| +
|
| + TODO: explain the rationale for this difference in treatment.
|
| +
|
| + """
|
| + if name == '.':
|
| + try:
|
| + return self.top()
|
| + except IndexError:
|
| + raise KeyNotFoundError(".", "empty context stack")
|
| +
|
| + parts = name.split('.')
|
| +
|
| + try:
|
| + result = self._get_simple(parts[0])
|
| + except KeyNotFoundError:
|
| + raise KeyNotFoundError(name, "first part")
|
| +
|
| + for part in parts[1:]:
|
| + # The full context stack is not used to resolve the remaining parts.
|
| + # From the spec--
|
| + #
|
| + # 5) If any name parts were retained in step 1, each should be
|
| + # resolved against a context stack containing only the result
|
| + # from the former resolution. If any part fails resolution, the
|
| + # result should be considered falsey, and should interpolate as
|
| + # the empty string.
|
| + #
|
| + # TODO: make sure we have a test case for the above point.
|
| + result = _get_value(result, part)
|
| + # TODO: consider using EAFP here instead.
|
| + # http://docs.python.org/glossary.html#term-eafp
|
| + if result is _NOT_FOUND:
|
| + raise KeyNotFoundError(name, "missing %s" % repr(part))
|
| +
|
| + return result
|
| +
|
| + def _get_simple(self, name):
|
| + """
|
| + Query the stack for a non-dotted name.
|
| +
|
| + """
|
| + for item in reversed(self._stack):
|
| + result = _get_value(item, name)
|
| + if result is not _NOT_FOUND:
|
| + return result
|
| +
|
| + raise KeyNotFoundError(name, "part missing")
|
| +
|
| + def push(self, item):
|
| + """
|
| + Push an item onto the stack.
|
| +
|
| + """
|
| + self._stack.append(item)
|
| +
|
| + def pop(self):
|
| + """
|
| + Pop an item off of the stack, and return it.
|
| +
|
| + """
|
| + return self._stack.pop()
|
| +
|
| + def top(self):
|
| + """
|
| + Return the item last added to the stack.
|
| +
|
| + """
|
| + return self._stack[-1]
|
| +
|
| + def copy(self):
|
| + """
|
| + Return a copy of this instance.
|
| +
|
| + """
|
| + return ContextStack(*self._stack)
|
|
|