Commit 93843675 authored by pkotwicz's avatar pkotwicz Committed by Commit Bot

Adding pystache to third_party

BUG=734131, 734136

Review-Url: https://codereview.chromium.org/2962783004
Cr-Commit-Position: refs/heads/master@{#488641}
parent e74f918c
Copyright (C) 2012 Chris Jerdonek. All rights reserved.
Copyright (c) 2009 Chris Wanstrath
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
hanxi@chromium.org
pkotwicz@chromium.org
yfriedman@chromium.org
Name: Pystache, a Python implementation of Mustache templates
Short Name: Pystache
URL: https://github.com/defunkt/pystache
Version: 0.5.4
Date: June 28, 2017
Revision: commit 17a5dfdcd56eb76af731d141de395a7632a905b8
License: MIT
License File: NOT_SHIPPED
Security Critical: No
Description:
Pystache is a Python implementation of Mustache. Mustache is a
framework-agnostic, logic-free templating system.
Local Modifications: None
"""
TODO: add a docstring.
"""
# We keep all initialization code in a separate module.
from pystache.init import parse, render, Renderer, TemplateSpec
__all__ = ['parse', 'render', 'Renderer', 'TemplateSpec']
__version__ = '0.5.4' # Also change in setup.py.
# coding: utf-8
"""
Exposes functionality needed throughout the project.
"""
from sys import version_info
def _get_string_types():
# TODO: come up with a better solution for this. One of the issues here
# is that in Python 3 there is no common base class for unicode strings
# and byte strings, and 2to3 seems to convert all of "str", "unicode",
# and "basestring" to Python 3's "str".
if version_info < (3, ):
return basestring
# The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3.
return (unicode, type(u"a".encode('utf-8')))
_STRING_TYPES = _get_string_types()
def is_string(obj):
"""
Return whether the given object is a byte string or unicode string.
This function is provided for compatibility with both Python 2 and 3
when using 2to3.
"""
return isinstance(obj, _STRING_TYPES)
# This function was designed to be portable across Python versions -- both
# with older versions and with Python 3 after applying 2to3.
def read(path):
"""
Return the contents of a text file as a byte string.
"""
# Opening in binary mode is necessary for compatibility across Python
# 2 and 3. In both Python 2 and 3, open() defaults to opening files in
# text mode. However, in Python 2, open() returns file objects whose
# read() method returns byte strings (strings of type `str` in Python 2),
# whereas in Python 3, the file object returns unicode strings (strings
# of type `str` in Python 3).
f = open(path, 'rb')
# We avoid use of the with keyword for Python 2.4 support.
try:
return f.read()
finally:
f.close()
class MissingTags(object):
"""Contains the valid values for Renderer.missing_tags."""
ignore = 'ignore'
strict = 'strict'
class PystacheError(Exception):
"""Base class for Pystache exceptions."""
pass
class TemplateNotFoundError(PystacheError):
"""An exception raised when a template is not found."""
pass
This diff is collapsed.
# coding: utf-8
"""
This module provides a central location for defining default behavior.
Throughout the package, these defaults take effect only when the user
does not otherwise specify a value.
"""
try:
# Python 3.2 adds html.escape() and deprecates cgi.escape().
from html import escape
except ImportError:
from cgi import escape
import os
import sys
from pystache.common import MissingTags
# How to handle encoding errors when decoding strings from str to unicode.
#
# This value is passed as the "errors" argument to Python's built-in
# unicode() function:
#
# http://docs.python.org/library/functions.html#unicode
#
DECODE_ERRORS = 'strict'
# The name of the encoding to use when converting to unicode any strings of
# type str encountered during the rendering process.
STRING_ENCODING = sys.getdefaultencoding()
# The name of the encoding to use when converting file contents to unicode.
# This default takes precedence over the STRING_ENCODING default for
# strings that arise from files.
FILE_ENCODING = sys.getdefaultencoding()
# The delimiters to start with when parsing.
DELIMITERS = (u'{{', u'}}')
# How to handle missing tags when rendering a template.
MISSING_TAGS = MissingTags.ignore
# The starting list of directories in which to search for templates when
# loading a template by file name.
SEARCH_DIRS = [os.curdir] # i.e. ['.']
# The escape function to apply to strings that require escaping when
# rendering templates (e.g. for tags enclosed in double braces).
# Only unicode strings will be passed to this function.
#
# The quote=True argument causes double but not single quotes to be escaped
# in Python 3.1 and earlier, and both double and single quotes to be
# escaped in Python 3.2 and later:
#
# http://docs.python.org/library/cgi.html#cgi.escape
# http://docs.python.org/dev/library/html.html#html.escape
#
TAG_ESCAPE = lambda u: escape(u, quote=True)
# The default template extension, without the leading dot.
TEMPLATE_EXTENSION = 'mustache'
# encoding: utf-8
"""
This module contains the initialization logic called by __init__.py.
"""
from pystache.parser import parse
from pystache.renderer import Renderer
from pystache.template_spec import TemplateSpec
def render(template, context=None, **kwargs):
"""
Return the given template string rendered using the given context.
"""
renderer = Renderer()
return renderer.render(template, context, **kwargs)
# coding: utf-8
"""
This module provides a Loader class for locating and reading templates.
"""
import os
import sys
from pystache import common
from pystache import defaults
from pystache.locator import Locator
# We make a function so that the current defaults take effect.
# TODO: revisit whether this is necessary.
def _make_to_unicode():
def to_unicode(s, encoding=None):
"""
Raises a TypeError exception if the given string is already unicode.
"""
if encoding is None:
encoding = defaults.STRING_ENCODING
return unicode(s, encoding, defaults.DECODE_ERRORS)
return to_unicode
class Loader(object):
"""
Loads the template associated to a name or user-defined object.
All load_*() methods return the template as a unicode string.
"""
def __init__(self, file_encoding=None, extension=None, to_unicode=None,
search_dirs=None):
"""
Construct a template loader instance.
Arguments:
extension: the template file extension, without the leading dot.
Pass False for no extension (e.g. to use extensionless template
files). Defaults to the package default.
file_encoding: the name of the encoding to use when converting file
contents to unicode. Defaults to the package default.
search_dirs: the list of directories in which to search when loading
a template by name or file name. Defaults to the package default.
to_unicode: the function to use when converting strings of type
str to unicode. The function should have the signature:
to_unicode(s, encoding=None)
It should accept a string of type str and an optional encoding
name and return a string of type unicode. Defaults to calling
Python's built-in function unicode() using the package string
encoding and decode errors defaults.
"""
if extension is None:
extension = defaults.TEMPLATE_EXTENSION
if file_encoding is None:
file_encoding = defaults.FILE_ENCODING
if search_dirs is None:
search_dirs = defaults.SEARCH_DIRS
if to_unicode is None:
to_unicode = _make_to_unicode()
self.extension = extension
self.file_encoding = file_encoding
# TODO: unit test setting this attribute.
self.search_dirs = search_dirs
self.to_unicode = to_unicode
def _make_locator(self):
return Locator(extension=self.extension)
def unicode(self, s, encoding=None):
"""
Convert a string to unicode using the given encoding, and return it.
This function uses the underlying to_unicode attribute.
Arguments:
s: a basestring instance to convert to unicode. Unlike Python's
built-in unicode() function, it is okay to pass unicode strings
to this function. (Passing a unicode string to Python's unicode()
with the encoding argument throws the error, "TypeError: decoding
Unicode is not supported.")
encoding: the encoding to pass to the to_unicode attribute.
Defaults to None.
"""
if isinstance(s, unicode):
return unicode(s)
return self.to_unicode(s, encoding)
def read(self, path, encoding=None):
"""
Read the template at the given path, and return it as a unicode string.
"""
b = common.read(path)
if encoding is None:
encoding = self.file_encoding
return self.unicode(b, encoding)
def load_file(self, file_name):
"""
Find and return the template with the given file name.
Arguments:
file_name: the file name of the template.
"""
locator = self._make_locator()
path = locator.find_file(file_name, self.search_dirs)
return self.read(path)
def load_name(self, name):
"""
Find and return the template with the given template name.
Arguments:
name: the name of the template.
"""
locator = self._make_locator()
path = locator.find_name(name, self.search_dirs)
return self.read(path)
# TODO: unit-test this method.
def load_object(self, obj):
"""
Find and return the template associated to the given object.
Arguments:
obj: an instance of a user-defined class.
search_dirs: the list of directories in which to search.
"""
locator = self._make_locator()
path = locator.find_object(obj, self.search_dirs)
return self.read(path)
# coding: utf-8
"""
This module provides a Locator class for finding template files.
"""
import os
import re
import sys
from pystache.common import TemplateNotFoundError
from pystache import defaults
class Locator(object):
def __init__(self, extension=None):
"""
Construct a template locator.
Arguments:
extension: the template file extension, without the leading dot.
Pass False for no extension (e.g. to use extensionless template
files). Defaults to the package default.
"""
if extension is None:
extension = defaults.TEMPLATE_EXTENSION
self.template_extension = extension
def get_object_directory(self, obj):
"""
Return the directory containing an object's defining class.
Returns None if there is no such directory, for example if the
class was defined in an interactive Python session, or in a
doctest that appears in a text file (rather than a Python file).
"""
if not hasattr(obj, '__module__'):
return None
module = sys.modules[obj.__module__]
if not hasattr(module, '__file__'):
# TODO: add a unit test for this case.
return None
path = module.__file__
return os.path.dirname(path)
def make_template_name(self, obj):
"""
Return the canonical template name for an object instance.
This method converts Python-style class names (PEP 8's recommended
CamelCase, aka CapWords) to lower_case_with_underscords. Here
is an example with code:
>>> class HelloWorld(object):
... pass
>>> hi = HelloWorld()
>>>
>>> locator = Locator()
>>> locator.make_template_name(hi)
'hello_world'
"""
template_name = obj.__class__.__name__
def repl(match):
return '_' + match.group(0).lower()
return re.sub('[A-Z]', repl, template_name)[1:]
def make_file_name(self, template_name, template_extension=None):
"""
Generate and return the file name for the given template name.
Arguments:
template_extension: defaults to the instance's extension.
"""
file_name = template_name
if template_extension is None:
template_extension = self.template_extension
if template_extension is not False:
file_name += os.path.extsep + template_extension
return file_name
def _find_path(self, search_dirs, file_name):
"""
Search for the given file, and return the path.
Returns None if the file is not found.
"""
for dir_path in search_dirs:
file_path = os.path.join(dir_path, file_name)
if os.path.exists(file_path):
return file_path
return None
def _find_path_required(self, search_dirs, file_name):
"""
Return the path to a template with the given file name.
"""
path = self._find_path(search_dirs, file_name)
if path is None:
raise TemplateNotFoundError('File %s not found in dirs: %s' %
(repr(file_name), repr(search_dirs)))
return path
def find_file(self, file_name, search_dirs):
"""
Return the path to a template with the given file name.
Arguments:
file_name: the file name of the template.
search_dirs: the list of directories in which to search.
"""
return self._find_path_required(search_dirs, file_name)
def find_name(self, template_name, search_dirs):
"""
Return the path to a template with the given name.
Arguments:
template_name: the name of the template.
search_dirs: the list of directories in which to search.
"""
file_name = self.make_file_name(template_name)
return self._find_path_required(search_dirs, file_name)
def find_object(self, obj, search_dirs, file_name=None):
"""
Return the path to a template associated with the given object.
"""
if file_name is None:
# TODO: should we define a make_file_name() method?
template_name = self.make_template_name(obj)
file_name = self.make_file_name(template_name)
dir_path = self.get_object_directory(obj)
if dir_path is not None:
search_dirs = [dir_path] + search_dirs
path = self._find_path_required(search_dirs, file_name)
return path
# coding: utf-8
"""
Exposes a class that represents a parsed (or compiled) template.
"""
class ParsedTemplate(object):
"""
Represents a parsed or compiled template.
An instance wraps a list of unicode strings and node objects. A node
object must have a `render(engine, stack)` method that accepts a
RenderEngine instance and a ContextStack instance and returns a unicode
string.
"""
def __init__(self):
self._parse_tree = []
def __repr__(self):
return repr(self._parse_tree)
def add(self, node):
"""
Arguments:
node: a unicode string or node object instance. See the class
docstring for information.
"""
self._parse_tree.append(node)
def render(self, engine, context):
"""
Returns: a string of type unicode.
"""
# We avoid use of the ternary operator for Python 2.4 support.
def get_unicode(node):
if type(node) is unicode:
return node
return node.render(engine, context)
parts = map(get_unicode, self._parse_tree)
s = ''.join(parts)
return unicode(s)
This diff is collapsed.
# coding: utf-8
"""
Defines a class responsible for rendering logic.
"""
import re
from pystache.common import is_string
from pystache.parser import parse
def context_get(stack, name):
"""
Find and return a name from a ContextStack instance.
"""
return stack.get(name)
class RenderEngine(object):
"""
Provides a render() method.
This class is meant only for internal use.
As a rule, the code in this class operates on unicode strings where
possible rather than, say, strings of type str or markupsafe.Markup.
This means that strings obtained from "external" sources like partials
and variable tag values are immediately converted to unicode (or
escaped and converted to unicode) before being operated on further.
This makes maintaining, reasoning about, and testing the correctness
of the code much simpler. In particular, it keeps the implementation
of this class independent of the API details of one (or possibly more)
unicode subclasses (e.g. markupsafe.Markup).
"""
# TODO: it would probably be better for the constructor to accept
# and set as an attribute a single RenderResolver instance
# that encapsulates the customizable aspects of converting
# strings and resolving partials and names from context.
def __init__(self, literal=None, escape=None, resolve_context=None,
resolve_partial=None, to_str=None):
"""
Arguments:
literal: the function used to convert unescaped variable tag
values to unicode, e.g. the value corresponding to a tag
"{{{name}}}". The function should accept a string of type
str or unicode (or a subclass) and return a string of type
unicode (but not a proper subclass of unicode).
This class will only pass basestring instances to this
function. For example, it will call str() on integer variable
values prior to passing them to this function.
escape: the function used to escape and convert variable tag
values to unicode, e.g. the value corresponding to a tag
"{{name}}". The function should obey the same properties
described above for the "literal" function argument.
This function should take care to convert any str
arguments to unicode just as the literal function should, as
this class will not pass tag values to literal prior to passing
them to this function. This allows for more flexibility,
for example using a custom escape function that handles
incoming strings of type markupsafe.Markup differently
from plain unicode strings.
resolve_context: the function to call to resolve a name against
a context stack. The function should accept two positional
arguments: a ContextStack instance and a name to resolve.
resolve_partial: the function to call when loading a partial.
The function should accept a template name string and return a
template string of type unicode (not a subclass).
to_str: a function that accepts an object and returns a string (e.g.
the built-in function str). This function is used for string
coercion whenever a string is required (e.g. for converting None
or 0 to a string).
"""
self.escape = escape
self.literal = literal
self.resolve_context = resolve_context
self.resolve_partial = resolve_partial
self.to_str = to_str
# TODO: Rename context to stack throughout this module.
# From the spec:
#
# When used as the data value for an Interpolation tag, the lambda
# MUST be treatable as an arity 0 function, and invoked as such.
# The returned value MUST be rendered against the default delimiters,
# then interpolated in place of the lambda.
#
def fetch_string(self, context, name):
"""
Get a value from the given context as a basestring instance.
"""
val = self.resolve_context(context, name)
if callable(val):
# Return because _render_value() is already a string.
return self._render_value(val(), context)
if not is_string(val):
return self.to_str(val)
return val
def fetch_section_data(self, context, name):
"""
Fetch the value of a section as a list.
"""
data = self.resolve_context(context, name)
# From the spec:
#
# If the data is not of a list type, it is coerced into a list
# as follows: if the data is truthy (e.g. `!!data == true`),
# use a single-element list containing the data, otherwise use
# an empty list.
#
if not data:
data = []
else:
# The least brittle way to determine whether something
# supports iteration is by trying to call iter() on it:
#
# http://docs.python.org/library/functions.html#iter
#
# It is not sufficient, for example, to check whether the item
# implements __iter__ () (the iteration protocol). There is
# also __getitem__() (the sequence protocol). In Python 2,
# strings do not implement __iter__(), but in Python 3 they do.
try:
iter(data)
except TypeError:
# Then the value does not support iteration.
data = [data]
else:
if is_string(data) or isinstance(data, dict):
# Do not treat strings and dicts (which are iterable) as lists.
data = [data]
# Otherwise, treat the value as a list.
return data
def _render_value(self, val, context, delimiters=None):
"""
Render an arbitrary value.
"""
if not is_string(val):
# In case the template is an integer, for example.
val = self.to_str(val)
if type(val) is not unicode:
val = self.literal(val)
return self.render(val, context, delimiters)
def render(self, template, context_stack, delimiters=None):
"""
Render a unicode template string, and return as unicode.
Arguments:
template: a template string of type unicode (but not a proper
subclass of unicode).
context_stack: a ContextStack instance.
"""
parsed_template = parse(template, delimiters)
return parsed_template.render(self, context_stack)
This diff is collapsed.
# coding: utf-8
"""
This module supports customized (aka special or specified) template loading.
"""
import os.path
from pystache.loader import Loader
# TODO: add test cases for this class.
class SpecLoader(object):
"""
Supports loading custom-specified templates (from TemplateSpec instances).
"""
def __init__(self, loader=None):
if loader is None:
loader = Loader()
self.loader = loader
def _find_relative(self, spec):
"""
Return the path to the template as a relative (dir, file_name) pair.
The directory returned is relative to the directory containing the
class definition of the given object. The method returns None for
this directory if the directory is unknown without first searching
the search directories.
"""
if spec.template_rel_path is not None:
return os.path.split(spec.template_rel_path)
# Otherwise, determine the file name separately.
locator = self.loader._make_locator()
# We do not use the ternary operator for Python 2.4 support.
if spec.template_name is not None:
template_name = spec.template_name
else:
template_name = locator.make_template_name(spec)
file_name = locator.make_file_name(template_name, spec.template_extension)
return (spec.template_rel_directory, file_name)
def _find(self, spec):
"""
Find and return the path to the template associated to the instance.
"""
if spec.template_path is not None:
return spec.template_path
dir_path, file_name = self._find_relative(spec)
locator = self.loader._make_locator()
if dir_path is None:
# Then we need to search for the path.
path = locator.find_object(spec, self.loader.search_dirs, file_name=file_name)
else:
obj_dir = locator.get_object_directory(spec)
path = os.path.join(obj_dir, dir_path, file_name)
return path
def load(self, spec):
"""
Find and return the template associated to a TemplateSpec instance.
Returns the template as a unicode string.
Arguments:
spec: a TemplateSpec instance.
"""
if spec.template is not None:
return self.loader.unicode(spec.template, spec.template_encoding)
path = self._find(spec)
return self.loader.read(path, spec.template_encoding)
# coding: utf-8
"""
Provides a class to customize template information on a per-view basis.
To customize template properties for a particular view, create that view
from a class that subclasses TemplateSpec. The "spec" in TemplateSpec
stands for "special" or "specified" template information.
"""
class TemplateSpec(object):
"""
A mixin or interface for specifying custom template information.
The "spec" in TemplateSpec can be taken to mean that the template
information is either "specified" or "special."
A view should subclass this class only if customized template loading
is needed. The following attributes allow one to customize/override
template information on a per view basis. A None value means to use
default behavior for that value and perform no customization. All
attributes are initialized to None.
Attributes:
template: the template as a string.
template_encoding: the encoding used by the template.
template_extension: the template file extension. Defaults to "mustache".
Pass False for no extension (i.e. extensionless template files).
template_name: the name of the template.
template_path: absolute path to the template.
template_rel_directory: the directory containing the template file,
relative to the directory containing the module defining the class.
template_rel_path: the path to the template file, relative to the
directory containing the module defining the class.
"""
template = None
template_encoding = None
template_extension = None
template_name = None
template_path = None
template_rel_directory = None
template_rel_path = None
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment