OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """ | 2 """ |
3 jinja2.loaders | 3 jinja2.loaders |
4 ~~~~~~~~~~~~~~ | 4 ~~~~~~~~~~~~~~ |
5 | 5 |
6 Jinja loader classes. | 6 Jinja loader classes. |
7 | 7 |
8 :copyright: (c) 2010 by the Jinja Team. | 8 :copyright: (c) 2010 by the Jinja Team. |
9 :license: BSD, see LICENSE for more details. | 9 :license: BSD, see LICENSE for more details. |
10 """ | 10 """ |
11 import os | 11 import os |
12 import sys | 12 import sys |
13 import weakref | 13 import weakref |
14 from types import ModuleType | 14 from types import ModuleType |
15 from os import path | 15 from os import path |
16 try: | 16 from hashlib import sha1 |
17 from hashlib import sha1 | |
18 except ImportError: | |
19 from sha import new as sha1 | |
20 from jinja2.exceptions import TemplateNotFound | 17 from jinja2.exceptions import TemplateNotFound |
21 from jinja2.utils import LRUCache, open_if_exists, internalcode | 18 from jinja2.utils import open_if_exists, internalcode |
| 19 from jinja2._compat import string_types, iteritems |
22 | 20 |
23 | 21 |
24 def split_template_path(template): | 22 def split_template_path(template): |
25 """Split a path into segments and perform a sanity check. If it detects | 23 """Split a path into segments and perform a sanity check. If it detects |
26 '..' in the path it will raise a `TemplateNotFound` error. | 24 '..' in the path it will raise a `TemplateNotFound` error. |
27 """ | 25 """ |
28 pieces = [] | 26 pieces = [] |
29 for piece in template.split('/'): | 27 for piece in template.split('/'): |
30 if path.sep in piece \ | 28 if path.sep in piece \ |
31 or (path.altsep and path.altsep in piece) or \ | 29 or (path.altsep and path.altsep in piece) or \ |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
146 given order: | 144 given order: |
147 | 145 |
148 >>> loader = FileSystemLoader('/path/to/templates') | 146 >>> loader = FileSystemLoader('/path/to/templates') |
149 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) | 147 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) |
150 | 148 |
151 Per default the template encoding is ``'utf-8'`` which can be changed | 149 Per default the template encoding is ``'utf-8'`` which can be changed |
152 by setting the `encoding` parameter to something else. | 150 by setting the `encoding` parameter to something else. |
153 """ | 151 """ |
154 | 152 |
155 def __init__(self, searchpath, encoding='utf-8'): | 153 def __init__(self, searchpath, encoding='utf-8'): |
156 if isinstance(searchpath, basestring): | 154 if isinstance(searchpath, string_types): |
157 searchpath = [searchpath] | 155 searchpath = [searchpath] |
158 self.searchpath = list(searchpath) | 156 self.searchpath = list(searchpath) |
159 self.encoding = encoding | 157 self.encoding = encoding |
160 | 158 |
161 def get_source(self, environment, template): | 159 def get_source(self, environment, template): |
162 pieces = split_template_path(template) | 160 pieces = split_template_path(template) |
163 for searchpath in self.searchpath: | 161 for searchpath in self.searchpath: |
164 filename = path.join(searchpath, *pieces) | 162 filename = path.join(searchpath, *pieces) |
165 f = open_if_exists(filename) | 163 f = open_if_exists(filename) |
166 if f is None: | 164 if f is None: |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
267 | 265 |
268 Because auto reloading is rarely useful this is disabled per default. | 266 Because auto reloading is rarely useful this is disabled per default. |
269 """ | 267 """ |
270 | 268 |
271 def __init__(self, mapping): | 269 def __init__(self, mapping): |
272 self.mapping = mapping | 270 self.mapping = mapping |
273 | 271 |
274 def get_source(self, environment, template): | 272 def get_source(self, environment, template): |
275 if template in self.mapping: | 273 if template in self.mapping: |
276 source = self.mapping[template] | 274 source = self.mapping[template] |
277 return source, None, lambda: source != self.mapping.get(template) | 275 return source, None, lambda: source == self.mapping.get(template) |
278 raise TemplateNotFound(template) | 276 raise TemplateNotFound(template) |
279 | 277 |
280 def list_templates(self): | 278 def list_templates(self): |
281 return sorted(self.mapping) | 279 return sorted(self.mapping) |
282 | 280 |
283 | 281 |
284 class FunctionLoader(BaseLoader): | 282 class FunctionLoader(BaseLoader): |
285 """A loader that is passed a function which does the loading. The | 283 """A loader that is passed a function which does the loading. The |
286 function becomes the name of the template passed and has to return either | 284 function becomes the name of the template passed and has to return either |
287 an unicode string with the template source, a tuple in the form ``(source, | 285 an unicode string with the template source, a tuple in the form ``(source, |
(...skipping 11 matching lines...) Expand all Loading... |
299 return value. | 297 return value. |
300 """ | 298 """ |
301 | 299 |
302 def __init__(self, load_func): | 300 def __init__(self, load_func): |
303 self.load_func = load_func | 301 self.load_func = load_func |
304 | 302 |
305 def get_source(self, environment, template): | 303 def get_source(self, environment, template): |
306 rv = self.load_func(template) | 304 rv = self.load_func(template) |
307 if rv is None: | 305 if rv is None: |
308 raise TemplateNotFound(template) | 306 raise TemplateNotFound(template) |
309 elif isinstance(rv, basestring): | 307 elif isinstance(rv, string_types): |
310 return rv, None, None | 308 return rv, None, None |
311 return rv | 309 return rv |
312 | 310 |
313 | 311 |
314 class PrefixLoader(BaseLoader): | 312 class PrefixLoader(BaseLoader): |
315 """A loader that is passed a dict of loaders where each loader is bound | 313 """A loader that is passed a dict of loaders where each loader is bound |
316 to a prefix. The prefix is delimited from the template by a slash per | 314 to a prefix. The prefix is delimited from the template by a slash per |
317 default, which can be changed by setting the `delimiter` argument to | 315 default, which can be changed by setting the `delimiter` argument to |
318 something else:: | 316 something else:: |
319 | 317 |
320 loader = PrefixLoader({ | 318 loader = PrefixLoader({ |
321 'app1': PackageLoader('mypackage.app1'), | 319 'app1': PackageLoader('mypackage.app1'), |
322 'app2': PackageLoader('mypackage.app2') | 320 'app2': PackageLoader('mypackage.app2') |
323 }) | 321 }) |
324 | 322 |
325 By loading ``'app1/index.html'`` the file from the app1 package is loaded, | 323 By loading ``'app1/index.html'`` the file from the app1 package is loaded, |
326 by loading ``'app2/index.html'`` the file from the second. | 324 by loading ``'app2/index.html'`` the file from the second. |
327 """ | 325 """ |
328 | 326 |
329 def __init__(self, mapping, delimiter='/'): | 327 def __init__(self, mapping, delimiter='/'): |
330 self.mapping = mapping | 328 self.mapping = mapping |
331 self.delimiter = delimiter | 329 self.delimiter = delimiter |
332 | 330 |
333 def get_source(self, environment, template): | 331 def get_loader(self, template): |
334 try: | 332 try: |
335 prefix, name = template.split(self.delimiter, 1) | 333 prefix, name = template.split(self.delimiter, 1) |
336 loader = self.mapping[prefix] | 334 loader = self.mapping[prefix] |
337 except (ValueError, KeyError): | 335 except (ValueError, KeyError): |
338 raise TemplateNotFound(template) | 336 raise TemplateNotFound(template) |
| 337 return loader, name |
| 338 |
| 339 def get_source(self, environment, template): |
| 340 loader, name = self.get_loader(template) |
339 try: | 341 try: |
340 return loader.get_source(environment, name) | 342 return loader.get_source(environment, name) |
341 except TemplateNotFound: | 343 except TemplateNotFound: |
342 # re-raise the exception with the correct fileame here. | 344 # re-raise the exception with the correct fileame here. |
343 # (the one that includes the prefix) | 345 # (the one that includes the prefix) |
344 raise TemplateNotFound(template) | 346 raise TemplateNotFound(template) |
345 | 347 |
| 348 @internalcode |
| 349 def load(self, environment, name, globals=None): |
| 350 loader, local_name = self.get_loader(name) |
| 351 try: |
| 352 return loader.load(environment, local_name) |
| 353 except TemplateNotFound: |
| 354 # re-raise the exception with the correct fileame here. |
| 355 # (the one that includes the prefix) |
| 356 raise TemplateNotFound(name) |
| 357 |
346 def list_templates(self): | 358 def list_templates(self): |
347 result = [] | 359 result = [] |
348 for prefix, loader in self.mapping.iteritems(): | 360 for prefix, loader in iteritems(self.mapping): |
349 for template in loader.list_templates(): | 361 for template in loader.list_templates(): |
350 result.append(prefix + self.delimiter + template) | 362 result.append(prefix + self.delimiter + template) |
351 return result | 363 return result |
352 | 364 |
353 | 365 |
354 class ChoiceLoader(BaseLoader): | 366 class ChoiceLoader(BaseLoader): |
355 """This loader works like the `PrefixLoader` just that no prefix is | 367 """This loader works like the `PrefixLoader` just that no prefix is |
356 specified. If a template could not be found by one loader the next one | 368 specified. If a template could not be found by one loader the next one |
357 is tried. | 369 is tried. |
358 | 370 |
(...skipping 10 matching lines...) Expand all Loading... |
369 self.loaders = loaders | 381 self.loaders = loaders |
370 | 382 |
371 def get_source(self, environment, template): | 383 def get_source(self, environment, template): |
372 for loader in self.loaders: | 384 for loader in self.loaders: |
373 try: | 385 try: |
374 return loader.get_source(environment, template) | 386 return loader.get_source(environment, template) |
375 except TemplateNotFound: | 387 except TemplateNotFound: |
376 pass | 388 pass |
377 raise TemplateNotFound(template) | 389 raise TemplateNotFound(template) |
378 | 390 |
| 391 @internalcode |
| 392 def load(self, environment, name, globals=None): |
| 393 for loader in self.loaders: |
| 394 try: |
| 395 return loader.load(environment, name, globals) |
| 396 except TemplateNotFound: |
| 397 pass |
| 398 raise TemplateNotFound(name) |
| 399 |
379 def list_templates(self): | 400 def list_templates(self): |
380 found = set() | 401 found = set() |
381 for loader in self.loaders: | 402 for loader in self.loaders: |
382 found.update(loader.list_templates()) | 403 found.update(loader.list_templates()) |
383 return sorted(found) | 404 return sorted(found) |
384 | 405 |
385 | 406 |
386 class _TemplateModule(ModuleType): | 407 class _TemplateModule(ModuleType): |
387 """Like a normal module but with support for weak references""" | 408 """Like a normal module but with support for weak references""" |
388 | 409 |
(...skipping 12 matching lines...) Expand all Loading... |
401 """ | 422 """ |
402 | 423 |
403 has_source_access = False | 424 has_source_access = False |
404 | 425 |
405 def __init__(self, path): | 426 def __init__(self, path): |
406 package_name = '_jinja2_module_templates_%x' % id(self) | 427 package_name = '_jinja2_module_templates_%x' % id(self) |
407 | 428 |
408 # create a fake module that looks for the templates in the | 429 # create a fake module that looks for the templates in the |
409 # path given. | 430 # path given. |
410 mod = _TemplateModule(package_name) | 431 mod = _TemplateModule(package_name) |
411 if isinstance(path, basestring): | 432 if isinstance(path, string_types): |
412 path = [path] | 433 path = [path] |
413 else: | 434 else: |
414 path = list(path) | 435 path = list(path) |
415 mod.__path__ = path | 436 mod.__path__ = path |
416 | 437 |
417 sys.modules[package_name] = weakref.proxy(mod, | 438 sys.modules[package_name] = weakref.proxy(mod, |
418 lambda x: sys.modules.pop(package_name, None)) | 439 lambda x: sys.modules.pop(package_name, None)) |
419 | 440 |
420 # the only strong reference, the sys.modules entry is weak | 441 # the only strong reference, the sys.modules entry is weak |
421 # so that the garbage collector can remove it once the | 442 # so that the garbage collector can remove it once the |
(...skipping 19 matching lines...) Expand all Loading... |
441 mod = __import__(module, None, None, ['root']) | 462 mod = __import__(module, None, None, ['root']) |
442 except ImportError: | 463 except ImportError: |
443 raise TemplateNotFound(name) | 464 raise TemplateNotFound(name) |
444 | 465 |
445 # remove the entry from sys.modules, we only want the attribute | 466 # remove the entry from sys.modules, we only want the attribute |
446 # on the module object we have stored on the loader. | 467 # on the module object we have stored on the loader. |
447 sys.modules.pop(module, None) | 468 sys.modules.pop(module, None) |
448 | 469 |
449 return environment.template_class.from_module_dict( | 470 return environment.template_class.from_module_dict( |
450 environment, mod.__dict__, globals) | 471 environment, mod.__dict__, globals) |
OLD | NEW |