mirror of
https://github.com/evilhero/mylar
synced 2024-12-22 15:52:47 +00:00
369 lines
13 KiB
Python
369 lines
13 KiB
Python
# mako/lookup.py
|
|
# Copyright (C) 2006-2016 the Mako authors and contributors <see AUTHORS file>
|
|
#
|
|
# This module is part of Mako and is released under
|
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
|
|
import os
|
|
import stat
|
|
import posixpath
|
|
import re
|
|
from mako import exceptions, util
|
|
from mako.template import Template
|
|
|
|
try:
|
|
import threading
|
|
except:
|
|
import dummy_threading as threading
|
|
|
|
|
|
class TemplateCollection(object):
|
|
|
|
"""Represent a collection of :class:`.Template` objects,
|
|
identifiable via URI.
|
|
|
|
A :class:`.TemplateCollection` is linked to the usage of
|
|
all template tags that address other templates, such
|
|
as ``<%include>``, ``<%namespace>``, and ``<%inherit>``.
|
|
The ``file`` attribute of each of those tags refers
|
|
to a string URI that is passed to that :class:`.Template`
|
|
object's :class:`.TemplateCollection` for resolution.
|
|
|
|
:class:`.TemplateCollection` is an abstract class,
|
|
with the usual default implementation being :class:`.TemplateLookup`.
|
|
|
|
"""
|
|
|
|
def has_template(self, uri):
|
|
"""Return ``True`` if this :class:`.TemplateLookup` is
|
|
capable of returning a :class:`.Template` object for the
|
|
given ``uri``.
|
|
|
|
:param uri: String URI of the template to be resolved.
|
|
|
|
"""
|
|
try:
|
|
self.get_template(uri)
|
|
return True
|
|
except exceptions.TemplateLookupException:
|
|
return False
|
|
|
|
def get_template(self, uri, relativeto=None):
|
|
"""Return a :class:`.Template` object corresponding to the given
|
|
``uri``.
|
|
|
|
The default implementation raises
|
|
:class:`.NotImplementedError`. Implementations should
|
|
raise :class:`.TemplateLookupException` if the given ``uri``
|
|
cannot be resolved.
|
|
|
|
:param uri: String URI of the template to be resolved.
|
|
:param relativeto: if present, the given ``uri`` is assumed to
|
|
be relative to this URI.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def filename_to_uri(self, uri, filename):
|
|
"""Convert the given ``filename`` to a URI relative to
|
|
this :class:`.TemplateCollection`."""
|
|
|
|
return uri
|
|
|
|
def adjust_uri(self, uri, filename):
|
|
"""Adjust the given ``uri`` based on the calling ``filename``.
|
|
|
|
When this method is called from the runtime, the
|
|
``filename`` parameter is taken directly to the ``filename``
|
|
attribute of the calling template. Therefore a custom
|
|
:class:`.TemplateCollection` subclass can place any string
|
|
identifier desired in the ``filename`` parameter of the
|
|
:class:`.Template` objects it constructs and have them come back
|
|
here.
|
|
|
|
"""
|
|
return uri
|
|
|
|
|
|
class TemplateLookup(TemplateCollection):
|
|
|
|
"""Represent a collection of templates that locates template source files
|
|
from the local filesystem.
|
|
|
|
The primary argument is the ``directories`` argument, the list of
|
|
directories to search:
|
|
|
|
.. sourcecode:: python
|
|
|
|
lookup = TemplateLookup(["/path/to/templates"])
|
|
some_template = lookup.get_template("/index.html")
|
|
|
|
The :class:`.TemplateLookup` can also be given :class:`.Template` objects
|
|
programatically using :meth:`.put_string` or :meth:`.put_template`:
|
|
|
|
.. sourcecode:: python
|
|
|
|
lookup = TemplateLookup()
|
|
lookup.put_string("base.html", '''
|
|
<html><body>${self.next()}</body></html>
|
|
''')
|
|
lookup.put_string("hello.html", '''
|
|
<%include file='base.html'/>
|
|
|
|
Hello, world !
|
|
''')
|
|
|
|
|
|
:param directories: A list of directory names which will be
|
|
searched for a particular template URI. The URI is appended
|
|
to each directory and the filesystem checked.
|
|
|
|
:param collection_size: Approximate size of the collection used
|
|
to store templates. If left at its default of ``-1``, the size
|
|
is unbounded, and a plain Python dictionary is used to
|
|
relate URI strings to :class:`.Template` instances.
|
|
Otherwise, a least-recently-used cache object is used which
|
|
will maintain the size of the collection approximately to
|
|
the number given.
|
|
|
|
:param filesystem_checks: When at its default value of ``True``,
|
|
each call to :meth:`.TemplateLookup.get_template()` will
|
|
compare the filesystem last modified time to the time in
|
|
which an existing :class:`.Template` object was created.
|
|
This allows the :class:`.TemplateLookup` to regenerate a
|
|
new :class:`.Template` whenever the original source has
|
|
been updated. Set this to ``False`` for a very minor
|
|
performance increase.
|
|
|
|
:param modulename_callable: A callable which, when present,
|
|
is passed the path of the source file as well as the
|
|
requested URI, and then returns the full path of the
|
|
generated Python module file. This is used to inject
|
|
alternate schemes for Python module location. If left at
|
|
its default of ``None``, the built in system of generation
|
|
based on ``module_directory`` plus ``uri`` is used.
|
|
|
|
All other keyword parameters available for
|
|
:class:`.Template` are mirrored here. When new
|
|
:class:`.Template` objects are created, the keywords
|
|
established with this :class:`.TemplateLookup` are passed on
|
|
to each new :class:`.Template`.
|
|
|
|
"""
|
|
|
|
def __init__(self,
|
|
directories=None,
|
|
module_directory=None,
|
|
filesystem_checks=True,
|
|
collection_size=-1,
|
|
format_exceptions=False,
|
|
error_handler=None,
|
|
disable_unicode=False,
|
|
bytestring_passthrough=False,
|
|
output_encoding=None,
|
|
encoding_errors='strict',
|
|
|
|
cache_args=None,
|
|
cache_impl='beaker',
|
|
cache_enabled=True,
|
|
cache_type=None,
|
|
cache_dir=None,
|
|
cache_url=None,
|
|
|
|
modulename_callable=None,
|
|
module_writer=None,
|
|
default_filters=None,
|
|
buffer_filters=(),
|
|
strict_undefined=False,
|
|
imports=None,
|
|
future_imports=None,
|
|
enable_loop=True,
|
|
input_encoding=None,
|
|
preprocessor=None,
|
|
lexer_cls=None,
|
|
include_error_handler=None):
|
|
|
|
self.directories = [posixpath.normpath(d) for d in
|
|
util.to_list(directories, ())
|
|
]
|
|
self.module_directory = module_directory
|
|
self.modulename_callable = modulename_callable
|
|
self.filesystem_checks = filesystem_checks
|
|
self.collection_size = collection_size
|
|
|
|
if cache_args is None:
|
|
cache_args = {}
|
|
# transfer deprecated cache_* args
|
|
if cache_dir:
|
|
cache_args.setdefault('dir', cache_dir)
|
|
if cache_url:
|
|
cache_args.setdefault('url', cache_url)
|
|
if cache_type:
|
|
cache_args.setdefault('type', cache_type)
|
|
|
|
self.template_args = {
|
|
'format_exceptions': format_exceptions,
|
|
'error_handler': error_handler,
|
|
'include_error_handler': include_error_handler,
|
|
'disable_unicode': disable_unicode,
|
|
'bytestring_passthrough': bytestring_passthrough,
|
|
'output_encoding': output_encoding,
|
|
'cache_impl': cache_impl,
|
|
'encoding_errors': encoding_errors,
|
|
'input_encoding': input_encoding,
|
|
'module_directory': module_directory,
|
|
'module_writer': module_writer,
|
|
'cache_args': cache_args,
|
|
'cache_enabled': cache_enabled,
|
|
'default_filters': default_filters,
|
|
'buffer_filters': buffer_filters,
|
|
'strict_undefined': strict_undefined,
|
|
'imports': imports,
|
|
'future_imports': future_imports,
|
|
'enable_loop': enable_loop,
|
|
'preprocessor': preprocessor,
|
|
'lexer_cls': lexer_cls
|
|
}
|
|
|
|
if collection_size == -1:
|
|
self._collection = {}
|
|
self._uri_cache = {}
|
|
else:
|
|
self._collection = util.LRUCache(collection_size)
|
|
self._uri_cache = util.LRUCache(collection_size)
|
|
self._mutex = threading.Lock()
|
|
|
|
def get_template(self, uri):
|
|
"""Return a :class:`.Template` object corresponding to the given
|
|
``uri``.
|
|
|
|
.. note:: The ``relativeto`` argument is not supported here at
|
|
the moment.
|
|
|
|
"""
|
|
|
|
try:
|
|
if self.filesystem_checks:
|
|
return self._check(uri, self._collection[uri])
|
|
else:
|
|
return self._collection[uri]
|
|
except KeyError:
|
|
u = re.sub(r'^\/+', '', uri)
|
|
for dir in self.directories:
|
|
# make sure the path seperators are posix - os.altsep is empty
|
|
# on POSIX and cannot be used.
|
|
dir = dir.replace(os.path.sep, posixpath.sep)
|
|
srcfile = posixpath.normpath(posixpath.join(dir, u))
|
|
if os.path.isfile(srcfile):
|
|
return self._load(srcfile, uri)
|
|
else:
|
|
raise exceptions.TopLevelLookupException(
|
|
"Cant locate template for uri %r" % uri)
|
|
|
|
def adjust_uri(self, uri, relativeto):
|
|
"""Adjust the given ``uri`` based on the given relative URI."""
|
|
|
|
key = (uri, relativeto)
|
|
if key in self._uri_cache:
|
|
return self._uri_cache[key]
|
|
|
|
if uri[0] != '/':
|
|
if relativeto is not None:
|
|
v = self._uri_cache[key] = posixpath.join(
|
|
posixpath.dirname(relativeto), uri)
|
|
else:
|
|
v = self._uri_cache[key] = '/' + uri
|
|
else:
|
|
v = self._uri_cache[key] = uri
|
|
return v
|
|
|
|
def filename_to_uri(self, filename):
|
|
"""Convert the given ``filename`` to a URI relative to
|
|
this :class:`.TemplateCollection`."""
|
|
|
|
try:
|
|
return self._uri_cache[filename]
|
|
except KeyError:
|
|
value = self._relativeize(filename)
|
|
self._uri_cache[filename] = value
|
|
return value
|
|
|
|
def _relativeize(self, filename):
|
|
"""Return the portion of a filename that is 'relative'
|
|
to the directories in this lookup.
|
|
|
|
"""
|
|
|
|
filename = posixpath.normpath(filename)
|
|
for dir in self.directories:
|
|
if filename[0:len(dir)] == dir:
|
|
return filename[len(dir):]
|
|
else:
|
|
return None
|
|
|
|
def _load(self, filename, uri):
|
|
self._mutex.acquire()
|
|
try:
|
|
try:
|
|
# try returning from collection one
|
|
# more time in case concurrent thread already loaded
|
|
return self._collection[uri]
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
if self.modulename_callable is not None:
|
|
module_filename = self.modulename_callable(filename, uri)
|
|
else:
|
|
module_filename = None
|
|
self._collection[uri] = template = Template(
|
|
uri=uri,
|
|
filename=posixpath.normpath(filename),
|
|
lookup=self,
|
|
module_filename=module_filename,
|
|
**self.template_args)
|
|
return template
|
|
except:
|
|
# if compilation fails etc, ensure
|
|
# template is removed from collection,
|
|
# re-raise
|
|
self._collection.pop(uri, None)
|
|
raise
|
|
finally:
|
|
self._mutex.release()
|
|
|
|
def _check(self, uri, template):
|
|
if template.filename is None:
|
|
return template
|
|
|
|
try:
|
|
template_stat = os.stat(template.filename)
|
|
if template.module._modified_time < \
|
|
template_stat[stat.ST_MTIME]:
|
|
self._collection.pop(uri, None)
|
|
return self._load(template.filename, uri)
|
|
else:
|
|
return template
|
|
except OSError:
|
|
self._collection.pop(uri, None)
|
|
raise exceptions.TemplateLookupException(
|
|
"Cant locate template for uri %r" % uri)
|
|
|
|
def put_string(self, uri, text):
|
|
"""Place a new :class:`.Template` object into this
|
|
:class:`.TemplateLookup`, based on the given string of
|
|
``text``.
|
|
|
|
"""
|
|
self._collection[uri] = Template(
|
|
text,
|
|
lookup=self,
|
|
uri=uri,
|
|
**self.template_args)
|
|
|
|
def put_template(self, uri, template):
|
|
"""Place a new :class:`.Template` object into this
|
|
:class:`.TemplateLookup`, based on the given
|
|
:class:`.Template` object.
|
|
|
|
"""
|
|
self._collection[uri] = template
|