template.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. # template.py
  2. # Copyright (C) 2006, 2007, 2008, 2009, 2010 Michael Bayer
  3. # mike_mp@zzzcomputing.com
  4. #
  5. # This module is part of Mako and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Provides the Template class, a facade for parsing, generating and executing
  8. template strings, as well as template runtime operations."""
  9. from mako.lexer import Lexer
  10. from mako import runtime, util, exceptions, codegen
  11. import imp, os, re, shutil, stat, sys, tempfile, time, types, weakref
  12. class Template(object):
  13. """a compiled template"""
  14. def __init__(self,
  15. text=None,
  16. filename=None,
  17. uri=None,
  18. format_exceptions=False,
  19. error_handler=None,
  20. lookup=None,
  21. output_encoding=None,
  22. encoding_errors='strict',
  23. module_directory=None,
  24. cache_type=None,
  25. cache_dir=None,
  26. cache_url=None,
  27. module_filename=None,
  28. input_encoding=None,
  29. disable_unicode=False,
  30. default_filters=None,
  31. buffer_filters=(),
  32. imports=None,
  33. preprocessor=None,
  34. cache_enabled=True):
  35. """Construct a new Template instance using either literal template
  36. text, or a previously loaded template module
  37. :param text: textual template source, or None if a module is to be
  38. provided
  39. :param uri: the uri of this template, or some identifying string.
  40. defaults to the full filename given, or "memory:(hex id of this
  41. Template)" if no filename
  42. :param filename: filename of the source template, if any
  43. :param format_exceptions: catch exceptions and format them into an
  44. error display template
  45. """
  46. if uri:
  47. self.module_id = re.sub(r'\W', "_", uri)
  48. self.uri = uri
  49. elif filename:
  50. self.module_id = re.sub(r'\W', "_", filename)
  51. drive, path = os.path.splitdrive(filename)
  52. path = os.path.normpath(path).replace(os.path.sep, "/")
  53. self.uri = path
  54. else:
  55. self.module_id = "memory:" + hex(id(self))
  56. self.uri = self.module_id
  57. self.input_encoding = input_encoding
  58. self.output_encoding = output_encoding
  59. self.encoding_errors = encoding_errors
  60. self.disable_unicode = disable_unicode
  61. if util.py3k and disable_unicode:
  62. raise exceptions.UnsupportedError(
  63. "Mako for Python 3 does not "
  64. "support disabling Unicode")
  65. if default_filters is None:
  66. if util.py3k or self.disable_unicode:
  67. self.default_filters = ['str']
  68. else:
  69. self.default_filters = ['unicode']
  70. else:
  71. self.default_filters = default_filters
  72. self.buffer_filters = buffer_filters
  73. self.imports = imports
  74. self.preprocessor = preprocessor
  75. # if plain text, compile code in memory only
  76. if text is not None:
  77. (code, module) = _compile_text(self, text, filename)
  78. self._code = code
  79. self._source = text
  80. ModuleInfo(module, None, self, filename, code, text)
  81. elif filename is not None:
  82. # if template filename and a module directory, load
  83. # a filesystem-based module file, generating if needed
  84. if module_filename is not None:
  85. path = module_filename
  86. elif module_directory is not None:
  87. u = self.uri
  88. if u[0] == '/':
  89. u = u[1:]
  90. path = os.path.abspath(
  91. os.path.join(
  92. os.path.normpath(module_directory),
  93. os.path.normpath(u) + ".py"
  94. )
  95. )
  96. else:
  97. path = None
  98. module = self._compile_from_file(path, filename)
  99. else:
  100. raise exceptions.RuntimeException(
  101. "Template requires text or filename")
  102. self.module = module
  103. self.filename = filename
  104. self.callable_ = self.module.render_body
  105. self.format_exceptions = format_exceptions
  106. self.error_handler = error_handler
  107. self.lookup = lookup
  108. self.cache_type = cache_type
  109. self.cache_dir = cache_dir
  110. self.cache_url = cache_url
  111. self.cache_enabled = cache_enabled
  112. def _compile_from_file(self, path, filename):
  113. if path is not None:
  114. util.verify_directory(os.path.dirname(path))
  115. filemtime = os.stat(filename)[stat.ST_MTIME]
  116. if not os.path.exists(path) or \
  117. os.stat(path)[stat.ST_MTIME] < filemtime:
  118. _compile_module_file(
  119. self,
  120. open(filename, 'rb').read(),
  121. filename,
  122. path)
  123. module = imp.load_source(self.module_id, path, open(path, 'rb'))
  124. del sys.modules[self.module_id]
  125. if module._magic_number != codegen.MAGIC_NUMBER:
  126. _compile_module_file(
  127. self,
  128. open(filename, 'rb').read(),
  129. filename,
  130. path)
  131. module = imp.load_source(self.module_id, path, open(path, 'rb'))
  132. del sys.modules[self.module_id]
  133. ModuleInfo(module, path, self, filename, None, None)
  134. else:
  135. # template filename and no module directory, compile code
  136. # in memory
  137. code, module = _compile_text(
  138. self,
  139. open(filename, 'rb').read(),
  140. filename)
  141. self._source = None
  142. self._code = code
  143. ModuleInfo(module, None, self, filename, code, None)
  144. return module
  145. @property
  146. def source(self):
  147. """return the template source code for this Template."""
  148. return _get_module_info_from_callable(self.callable_).source
  149. @property
  150. def code(self):
  151. """return the module source code for this Template"""
  152. return _get_module_info_from_callable(self.callable_).code
  153. @property
  154. def cache(self):
  155. return self.module._template_cache
  156. def render(self, *args, **data):
  157. """Render the output of this template as a string.
  158. if the template specifies an output encoding, the string will be
  159. encoded accordingly, else the output is raw (raw output uses cStringIO
  160. and can't handle multibyte characters). a Context object is created
  161. corresponding to the given data. Arguments that are explictly declared
  162. by this template's internal rendering method are also pulled from the
  163. given \*args, \**data members.
  164. """
  165. return runtime._render(self, self.callable_, args, data)
  166. def render_unicode(self, *args, **data):
  167. """render the output of this template as a unicode object."""
  168. return runtime._render(self,
  169. self.callable_,
  170. args,
  171. data,
  172. as_unicode=True)
  173. def render_context(self, context, *args, **kwargs):
  174. """Render this Template with the given context.
  175. the data is written to the context's buffer.
  176. """
  177. if getattr(context, '_with_template', None) is None:
  178. context._with_template = self
  179. runtime._render_context(self,
  180. self.callable_,
  181. context,
  182. *args,
  183. **kwargs)
  184. def has_def(self, name):
  185. return hasattr(self.module, "render_%s" % name)
  186. def get_def(self, name):
  187. """Return a def of this template as a DefTemplate."""
  188. return DefTemplate(self, getattr(self.module, "render_%s" % name))
  189. def _get_def_callable(self, name):
  190. return getattr(self.module, "render_%s" % name)
  191. @property
  192. def last_modified(self):
  193. return self.module._modified_time
  194. class ModuleTemplate(Template):
  195. """A Template which is constructed given an existing Python module.
  196. e.g.::
  197. t = Template("this is a template")
  198. f = file("mymodule.py", "w")
  199. f.write(t.code)
  200. f.close()
  201. import mymodule
  202. t = ModuleTemplate(mymodule)
  203. print t.render()
  204. """
  205. def __init__(self, module,
  206. module_filename=None,
  207. template=None,
  208. template_filename=None,
  209. module_source=None,
  210. template_source=None,
  211. output_encoding=None,
  212. encoding_errors='strict',
  213. disable_unicode=False,
  214. format_exceptions=False,
  215. error_handler=None,
  216. lookup=None,
  217. cache_type=None,
  218. cache_dir=None,
  219. cache_url=None,
  220. cache_enabled=True
  221. ):
  222. self.module_id = re.sub(r'\W', "_", module._template_uri)
  223. self.uri = module._template_uri
  224. self.input_encoding = module._source_encoding
  225. self.output_encoding = output_encoding
  226. self.encoding_errors = encoding_errors
  227. self.disable_unicode = disable_unicode
  228. self.module = module
  229. self.filename = template_filename
  230. ModuleInfo(module,
  231. module_filename,
  232. self,
  233. template_filename,
  234. module_source,
  235. template_source)
  236. self.callable_ = self.module.render_body
  237. self.format_exceptions = format_exceptions
  238. self.error_handler = error_handler
  239. self.lookup = lookup
  240. self.cache_type = cache_type
  241. self.cache_dir = cache_dir
  242. self.cache_url = cache_url
  243. self.cache_enabled = cache_enabled
  244. class DefTemplate(Template):
  245. """a Template which represents a callable def in a parent template."""
  246. def __init__(self, parent, callable_):
  247. self.parent = parent
  248. self.callable_ = callable_
  249. self.output_encoding = parent.output_encoding
  250. self.module = parent.module
  251. self.encoding_errors = parent.encoding_errors
  252. self.format_exceptions = parent.format_exceptions
  253. self.error_handler = parent.error_handler
  254. self.lookup = parent.lookup
  255. def get_def(self, name):
  256. return self.parent.get_def(name)
  257. class ModuleInfo(object):
  258. """Stores information about a module currently loaded into memory,
  259. provides reverse lookups of template source, module source code based on
  260. a module's identifier.
  261. """
  262. _modules = weakref.WeakValueDictionary()
  263. def __init__(self,
  264. module,
  265. module_filename,
  266. template,
  267. template_filename,
  268. module_source,
  269. template_source):
  270. self.module = module
  271. self.module_filename = module_filename
  272. self.template_filename = template_filename
  273. self.module_source = module_source
  274. self.template_source = template_source
  275. self._modules[module.__name__] = template._mmarker = self
  276. if module_filename:
  277. self._modules[module_filename] = self
  278. @property
  279. def code(self):
  280. if self.module_source is not None:
  281. return self.module_source
  282. else:
  283. return open(self.module_filename).read()
  284. @property
  285. def source(self):
  286. if self.template_source is not None:
  287. if self.module._source_encoding and \
  288. not isinstance(self.template_source, unicode):
  289. return self.template_source.decode(
  290. self.module._source_encoding)
  291. else:
  292. return self.template_source
  293. else:
  294. if self.module._source_encoding:
  295. return open(self.template_filename, 'rb').read().\
  296. decode(self.module._source_encoding)
  297. else:
  298. return open(self.template_filename).read()
  299. def _compile_text(template, text, filename):
  300. identifier = template.module_id
  301. lexer = Lexer(text,
  302. filename,
  303. disable_unicode=template.disable_unicode,
  304. input_encoding=template.input_encoding,
  305. preprocessor=template.preprocessor)
  306. node = lexer.parse()
  307. source = codegen.compile(node,
  308. template.uri,
  309. filename,
  310. default_filters=template.default_filters,
  311. buffer_filters=template.buffer_filters,
  312. imports=template.imports,
  313. source_encoding=lexer.encoding,
  314. generate_magic_comment=template.disable_unicode,
  315. disable_unicode=template.disable_unicode)
  316. cid = identifier
  317. if not util.py3k and isinstance(cid, unicode):
  318. cid = cid.encode()
  319. module = types.ModuleType(cid)
  320. code = compile(source, cid, 'exec')
  321. exec code in module.__dict__, module.__dict__
  322. return (source, module)
  323. def _compile_module_file(template, text, filename, outputpath):
  324. identifier = template.module_id
  325. lexer = Lexer(text,
  326. filename,
  327. disable_unicode=template.disable_unicode,
  328. input_encoding=template.input_encoding,
  329. preprocessor=template.preprocessor)
  330. node = lexer.parse()
  331. source = codegen.compile(node,
  332. template.uri,
  333. filename,
  334. default_filters=template.default_filters,
  335. buffer_filters=template.buffer_filters,
  336. imports=template.imports,
  337. source_encoding=lexer.encoding,
  338. generate_magic_comment=True,
  339. disable_unicode=template.disable_unicode)
  340. # make tempfiles in the same location as the ultimate
  341. # location. this ensures they're on the same filesystem,
  342. # avoiding synchronization issues.
  343. (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath))
  344. if isinstance(source, unicode):
  345. source = source.encode(lexer.encoding or 'ascii')
  346. os.write(dest, source)
  347. os.close(dest)
  348. shutil.move(name, outputpath)
  349. def _get_module_info_from_callable(callable_):
  350. return _get_module_info(callable_.func_globals['__name__'])
  351. def _get_module_info(filename):
  352. return ModuleInfo._modules[filename]