Source code for score.tpl.loader
# Copyright © 2015-2017 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.
from ._exc import TemplateNotFound
import abc
import os
import fnmatch
import xxhash
[docs]class Loader:
"""
Object capable of loading template content.
"""
@property
def paths(self):
return list(self.iter_paths())
[docs] @abc.abstractmethod
def iter_paths(self):
"""
Provide a generator iterating all valid paths of this loader.
"""
pass
[docs] @abc.abstractmethod
def load(self, path):
"""
Load given *path*. Returns a 2-tuple, where the first value is a `bool`
indicating whether the other value is a file path (`True`) or the
contents of the template (`False`). Illustration of the return values:
.. code-block:: python
(True, '/path/to/the/template.html')
(False, '<html>The contents of the template</html>')
Raises :class:`TemplateNotFound` if this loader cannot load the given
path.
"""
pass
[docs] def is_valid(self, path):
"""
Whether given *path* is valid for this loader, i.e. whether a call to
:meth:`load` would return successfully.
"""
return path in self.iter_paths()
[docs] def hash(self, path):
"""
Provides a random `str`, that will always change whenever the file
content changes. The default implementation uses the content's xxHash_.
.. _xxHash: http://cyan4973.github.io/xxHash/
"""
is_file, result = self.load(path)
if is_file:
try:
hash = xxhash.xxh64()
with open(result, 'rb') as file:
for chunk in iter(lambda: file.read(4096), b""):
hash.update(chunk)
return hash.hexdigest()
except FileNotFoundError:
raise TemplateNotFound(path)
else:
if isinstance(result, str):
result = result.encode('ASCII')
return xxhash.xxh64(result).hexdigest()
[docs]class FileSystemLoader(Loader):
"""
:class:`Loader` searching for files with a given *extension* inside given
folders.
"""
def __init__(self, rootdirs, extension):
if isinstance(rootdirs, str):
rootdirs = [rootdirs]
self.rootdirs = rootdirs
self.extension = extension
def iter_paths(self):
if not self.rootdirs:
return
found = []
filter = '*.%s' % (self.extension,)
for rootdir in self.rootdirs:
for base, dirs, files in os.walk(rootdir, followlinks=True):
for filename in fnmatch.filter(files, filter):
path = os.path.join(base, filename)
path = os.path.relpath(path, rootdir)
if path in found:
continue
found.append(path)
yield path
def load(self, path):
for rootdir in self.rootdirs:
fullpath = os.path.join(rootdir, path.lstrip('/'))
relpath = os.path.relpath(fullpath, rootdir)
if relpath.startswith(os.pardir + os.sep):
# outside of rootdir
continue
if os.path.exists(fullpath):
return True, fullpath
raise TemplateNotFound(path)
[docs]class ChainLoader(Loader):
"""
A :class:`Loader` that will wrap the other given *loaders* and simulate a
loader, that combines the features of all of them. If this receives a Loader
capable of loading 'a.tpl' and another Loader that can load 'b.tpl', this
ChainLoader instance will be able to load 'a.tpl' *and* 'b.tpl'.
"""
def __init__(self, loaders):
self.loaders = loaders
def iter_paths(self):
for loader in self.loaders:
yield from loader.iter_paths()
def load(self, path):
for loader in self.loaders:
try:
return loader.load(path)
except TemplateNotFound:
pass
raise TemplateNotFound(path)
def hash(self, path):
for loader in self.loaders:
try:
return loader.hash(path)
except TemplateNotFound:
pass