codekingpro/portable-devtools
114k
1# mako/lookup.py2# Copyright 2006-2024 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6 7import os8import posixpath9import re10import stat11import threading12 13from mako import exceptions14from mako import util15from mako.template import Template16 17 18class TemplateCollection:19 20 """Represent a collection of :class:`.Template` objects,21 identifiable via URI.22 23 A :class:`.TemplateCollection` is linked to the usage of24 all template tags that address other templates, such25 as ``<%include>``, ``<%namespace>``, and ``<%inherit>``.26 The ``file`` attribute of each of those tags refers27 to a string URI that is passed to that :class:`.Template`28 object's :class:`.TemplateCollection` for resolution.29 30 :class:`.TemplateCollection` is an abstract class,31 with the usual default implementation being :class:`.TemplateLookup`.32 33 """34 35 def has_template(self, uri):36 """Return ``True`` if this :class:`.TemplateLookup` is37 capable of returning a :class:`.Template` object for the38 given ``uri``.39 40 :param uri: String URI of the template to be resolved.41 42 """43 try:44 self.get_template(uri)45 return True46 except exceptions.TemplateLookupException:47 return False48 49 def get_template(self, uri, relativeto=None):50 """Return a :class:`.Template` object corresponding to the given51 ``uri``.52 53 The default implementation raises54 :class:`.NotImplementedError`. Implementations should55 raise :class:`.TemplateLookupException` if the given ``uri``56 cannot be resolved.57 58 :param uri: String URI of the template to be resolved.59 :param relativeto: if present, the given ``uri`` is assumed to60 be relative to this URI.61 62 """63 raise NotImplementedError()64 65 def filename_to_uri(self, uri, filename):66 """Convert the given ``filename`` to a URI relative to67 this :class:`.TemplateCollection`."""68 69 return uri70 71 def adjust_uri(self, uri, filename):72 """Adjust the given ``uri`` based on the calling ``filename``.73 74 When this method is called from the runtime, the75 ``filename`` parameter is taken directly to the ``filename``76 attribute of the calling template. Therefore a custom77 :class:`.TemplateCollection` subclass can place any string78 identifier desired in the ``filename`` parameter of the79 :class:`.Template` objects it constructs and have them come back80 here.81 82 """83 return uri84 85 86class TemplateLookup(TemplateCollection):87 88 """Represent a collection of templates that locates template source files89 from the local filesystem.90 91 The primary argument is the ``directories`` argument, the list of92 directories to search:93 94 .. sourcecode:: python95 96 lookup = TemplateLookup(["/path/to/templates"])97 some_template = lookup.get_template("/index.html")98 99 The :class:`.TemplateLookup` can also be given :class:`.Template` objects100 programatically using :meth:`.put_string` or :meth:`.put_template`:101 102 .. sourcecode:: python103 104 lookup = TemplateLookup()105 lookup.put_string("base.html", '''106 <html><body>${self.next()}</body></html>107 ''')108 lookup.put_string("hello.html", '''109 <%include file='base.html'/>110 111 Hello, world !112 ''')113 114 115 :param directories: A list of directory names which will be116 searched for a particular template URI. The URI is appended117 to each directory and the filesystem checked.118 119 :param collection_size: Approximate size of the collection used120 to store templates. If left at its default of ``-1``, the size121 is unbounded, and a plain Python dictionary is used to122 relate URI strings to :class:`.Template` instances.123 Otherwise, a least-recently-used cache object is used which124 will maintain the size of the collection approximately to125 the number given.126 127 :param filesystem_checks: When at its default value of ``True``,128 each call to :meth:`.TemplateLookup.get_template()` will129 compare the filesystem last modified time to the time in130 which an existing :class:`.Template` object was created.131 This allows the :class:`.TemplateLookup` to regenerate a132 new :class:`.Template` whenever the original source has133 been updated. Set this to ``False`` for a very minor134 performance increase.135 136 :param modulename_callable: A callable which, when present,137 is passed the path of the source file as well as the138 requested URI, and then returns the full path of the139 generated Python module file. This is used to inject140 alternate schemes for Python module location. If left at141 its default of ``None``, the built in system of generation142 based on ``module_directory`` plus ``uri`` is used.143 144 All other keyword parameters available for145 :class:`.Template` are mirrored here. When new146 :class:`.Template` objects are created, the keywords147 established with this :class:`.TemplateLookup` are passed on148 to each new :class:`.Template`.149 150 """151 152 def __init__(153 self,154 directories=None,155 module_directory=None,156 filesystem_checks=True,157 collection_size=-1,158 format_exceptions=False,159 error_handler=None,160 output_encoding=None,161 encoding_errors="strict",162 cache_args=None,163 cache_impl="beaker",164 cache_enabled=True,165 cache_type=None,166 cache_dir=None,167 cache_url=None,168 modulename_callable=None,169 module_writer=None,170 default_filters=None,171 buffer_filters=(),172 strict_undefined=False,173 imports=None,174 future_imports=None,175 enable_loop=True,176 input_encoding=None,177 preprocessor=None,178 lexer_cls=None,179 include_error_handler=None,180 ):181 self.directories = [182 posixpath.normpath(d) for d in util.to_list(directories, ())183 ]184 self.module_directory = module_directory185 self.modulename_callable = modulename_callable186 self.filesystem_checks = filesystem_checks187 self.collection_size = collection_size188 189 if cache_args is None:190 cache_args = {}191 # transfer deprecated cache_* args192 if cache_dir:193 cache_args.setdefault("dir", cache_dir)194 if cache_url:195 cache_args.setdefault("url", cache_url)196 if cache_type:197 cache_args.setdefault("type", cache_type)198 199 self.template_args = {200 "format_exceptions": format_exceptions,201 "error_handler": error_handler,202 "include_error_handler": include_error_handler,203 "output_encoding": output_encoding,204 "cache_impl": cache_impl,205 "encoding_errors": encoding_errors,206 "input_encoding": input_encoding,207 "module_directory": module_directory,208 "module_writer": module_writer,209 "cache_args": cache_args,210 "cache_enabled": cache_enabled,211 "default_filters": default_filters,212 "buffer_filters": buffer_filters,213 "strict_undefined": strict_undefined,214 "imports": imports,215 "future_imports": future_imports,216 "enable_loop": enable_loop,217 "preprocessor": preprocessor,218 "lexer_cls": lexer_cls,219 }220 221 if collection_size == -1:222 self._collection = {}223 self._uri_cache = {}224 else:225 self._collection = util.LRUCache(collection_size)226 self._uri_cache = util.LRUCache(collection_size)227 self._mutex = threading.Lock()228 229 def get_template(self, uri):230 """Return a :class:`.Template` object corresponding to the given231 ``uri``.232 233 .. note:: The ``relativeto`` argument is not supported here at234 the moment.235 236 """237 238 try:239 if self.filesystem_checks:240 return self._check(uri, self._collection[uri])241 else:242 return self._collection[uri]243 except KeyError as e:244 u = re.sub(r"^\/+", "", uri)245 for dir_ in self.directories:246 # make sure the path seperators are posix - os.altsep is empty247 # on POSIX and cannot be used.248 dir_ = dir_.replace(os.path.sep, posixpath.sep)249 srcfile = posixpath.normpath(posixpath.join(dir_, u))250 if os.path.isfile(srcfile):251 return self._load(srcfile, uri)252 else:253 raise exceptions.TopLevelLookupException(254 "Can't locate template for uri %r" % uri255 ) from e256 257 def adjust_uri(self, uri, relativeto):258 """Adjust the given ``uri`` based on the given relative URI."""259 260 key = (uri, relativeto)261 if key in self._uri_cache:262 return self._uri_cache[key]263 264 if uri[0] == "/":265 v = self._uri_cache[key] = uri266 elif relativeto is not None:267 v = self._uri_cache[key] = posixpath.join(268 posixpath.dirname(relativeto), uri269 )270 else:271 v = self._uri_cache[key] = "/" + uri272 return v273 274 def filename_to_uri(self, filename):275 """Convert the given ``filename`` to a URI relative to276 this :class:`.TemplateCollection`."""277 278 try:279 return self._uri_cache[filename]280 except KeyError:281 value = self._relativeize(filename)282 self._uri_cache[filename] = value283 return value284 285 def _relativeize(self, filename):286 """Return the portion of a filename that is 'relative'287 to the directories in this lookup.288 289 """290 291 filename = posixpath.normpath(filename)292 for dir_ in self.directories:293 if filename[0 : len(dir_)] == dir_:294 return filename[len(dir_) :]295 else:296 return None297 298 def _load(self, filename, uri):299 self._mutex.acquire()300 try:301 try:302 # try returning from collection one303 # more time in case concurrent thread already loaded304 return self._collection[uri]305 except KeyError:306 pass307 try:308 if self.modulename_callable is not None:309 module_filename = self.modulename_callable(filename, uri)310 else:311 module_filename = None312 self._collection[uri] = template = Template(313 uri=uri,314 filename=posixpath.normpath(filename),315 lookup=self,316 module_filename=module_filename,317 **self.template_args,318 )319 return template320 except:321 # if compilation fails etc, ensure322 # template is removed from collection,323 # re-raise324 self._collection.pop(uri, None)325 raise326 finally:327 self._mutex.release()328 329 def _check(self, uri, template):330 if template.filename is None:331 return template332 333 try:334 template_stat = os.stat(template.filename)335 if template.module._modified_time >= template_stat[stat.ST_MTIME]:336 return template337 self._collection.pop(uri, None)338 return self._load(template.filename, uri)339 except OSError as e:340 self._collection.pop(uri, None)341 raise exceptions.TemplateLookupException(342 "Can't locate template for uri %r" % uri343 ) from e344 345 def put_string(self, uri, text):346 """Place a new :class:`.Template` object into this347 :class:`.TemplateLookup`, based on the given string of348 ``text``.349 350 """351 self._collection[uri] = Template(352 text, lookup=self, uri=uri, **self.template_args353 )354 355 def put_template(self, uri, template):356 """Place a new :class:`.Template` object into this357 :class:`.TemplateLookup`, based on the given358 :class:`.Template` object.359 360 """361 self._collection[uri] = template362 