PageRenderTime 2093ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/packages/werkzeug/templates.py

https://gitlab.com/gregtyka/Scryve-Webapp
Python | 392 lines | 371 code | 4 blank | 17 comment | 0 complexity | 8fbe76dc0fb2a7a56b24a8cd17d96cca MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. r"""
  3. werkzeug.templates
  4. ~~~~~~~~~~~~~~~~~~
  5. A minimal template engine.
  6. :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
  7. :license: BSD License.
  8. """
  9. import sys
  10. import re
  11. import __builtin__ as builtins
  12. from compiler import ast, parse
  13. from compiler.pycodegen import ModuleCodeGenerator
  14. from tokenize import PseudoToken
  15. from werkzeug import utils, urls
  16. from werkzeug._internal import _decode_unicode
  17. # Copyright notice: The `parse_data` method uses the string interpolation
  18. # algorithm by Ka-Ping Yee which originally was part of `Itpl20.py`_.
  19. #
  20. # .. _Itpl20.py: http://lfw.org/python/Itpl20.py
  21. token_re = re.compile('%s|%s(?s)' % (
  22. r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1',
  23. PseudoToken
  24. ))
  25. directive_re = re.compile(r'(?<!\\)<%(?:(#)|(py(?:thon)?\b)|'
  26. r'(?:\s*(\w+))\s*)(.*?)\s*%>\n?(?s)')
  27. escape_re = re.compile(r'\\\n|\\(\\|<%)')
  28. namestart_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
  29. undefined = type('UndefinedType', (object,), {
  30. '__iter__': lambda x: iter(()),
  31. '__repr__': lambda x: 'Undefined',
  32. '__str__': lambda x: ''
  33. })()
  34. runtime_vars = dict.fromkeys(('Undefined', '__to_unicode', '__context',
  35. '__write', '__write_many'))
  36. def call_stmt(func, args, lineno):
  37. return ast.CallFunc(ast.Name(func, lineno=lineno),
  38. args, lineno=lineno)
  39. def tokenize(source, filename):
  40. escape = escape_re.sub
  41. escape_repl = lambda m: m.group(1) or ''
  42. lineno = 1
  43. pos = 0
  44. for match in directive_re.finditer(source):
  45. start, end = match.span()
  46. if start > pos:
  47. data = source[pos:start]
  48. yield lineno, 'data', escape(escape_repl, data)
  49. lineno += data.count('\n')
  50. is_comment, is_code, cmd, args = match.groups()
  51. if is_code:
  52. yield lineno, 'code', args
  53. elif not is_comment:
  54. yield lineno, 'cmd', (cmd, args)
  55. lineno += source[start:end].count('\n')
  56. pos = end
  57. if pos < len(source):
  58. yield lineno, 'data', escape(escape_repl, source[pos:])
  59. def transform(node, filename):
  60. root = ast.Module(None, node, lineno=1)
  61. nodes = [root]
  62. while nodes:
  63. node = nodes.pop()
  64. node.filename = filename
  65. if node.__class__ in (ast.Printnl, ast.Print):
  66. node.dest = ast.Name('__context')
  67. elif node.__class__ is ast.Const and isinstance(node.value, str):
  68. try:
  69. node.value.decode('ascii')
  70. except UnicodeError:
  71. node.value = node.value.decode('utf-8')
  72. nodes.extend(node.getChildNodes())
  73. return root
  74. class TemplateSyntaxError(SyntaxError):
  75. def __init__(self, msg, filename, lineno):
  76. from linecache import getline
  77. l = getline(filename, lineno)
  78. SyntaxError.__init__(self, msg, (filename, lineno, len(l) or 1, l))
  79. class Parser(object):
  80. def __init__(self, gen, filename):
  81. self.gen = gen
  82. self.filename = filename
  83. self.lineno = 1
  84. def fail(self, msg):
  85. raise TemplateSyntaxError(msg, self.filename, self.lineno)
  86. def parse_python(self, expr, type='exec'):
  87. if isinstance(expr, unicode):
  88. expr = '\xef\xbb\xbf' + expr.encode('utf-8')
  89. try:
  90. node = parse(expr, type)
  91. except SyntaxError, e:
  92. raise TemplateSyntaxError(str(e), self.filename,
  93. self.lineno + e.lineno - 1)
  94. nodes = [node]
  95. while nodes:
  96. n = nodes.pop()
  97. if hasattr(n, 'lineno'):
  98. n.lineno = (n.lineno or 1) + self.lineno - 1
  99. nodes.extend(n.getChildNodes())
  100. return node.node
  101. def parse(self, needle=()):
  102. start_lineno = self.lineno
  103. result = []
  104. add = result.append
  105. for self.lineno, token, value in self.gen:
  106. if token == 'data':
  107. add(self.parse_data(value))
  108. elif token == 'code':
  109. add(self.parse_code(value.splitlines()))
  110. elif token == 'cmd':
  111. name, args = value
  112. if name in needle:
  113. return name, args, ast.Stmt(result, lineno=start_lineno)
  114. if name in ('for', 'while'):
  115. add(self.parse_loop(args, name))
  116. elif name == 'if':
  117. add(self.parse_if(args))
  118. else:
  119. self.fail('unknown directive %s' % name)
  120. if needle:
  121. self.fail('unexpected end of template')
  122. return ast.Stmt(result, lineno=start_lineno)
  123. def parse_loop(self, args, type):
  124. rv = self.parse_python('%s %s: pass' % (type, args), 'exec').nodes[0]
  125. tag, value, rv.body = self.parse(('end' + type, 'else'))
  126. if value:
  127. self.fail('unexpected data after ' + tag)
  128. if tag == 'else':
  129. tag, value, rv.else_ = self.parse(('end' + type,))
  130. if value:
  131. self.fail('unexpected data after else')
  132. return rv
  133. def parse_if(self, args):
  134. cond = self.parse_python('if %s: pass' % args).nodes[0]
  135. tag, value, body = self.parse(('else', 'elif', 'endif'))
  136. cond.tests[0] = (cond.tests[0][0], body)
  137. while 1:
  138. if tag == 'else':
  139. if value:
  140. self.fail('unexpected data after else')
  141. tag, value, cond.else_ = self.parse(('endif',))
  142. elif tag == 'elif':
  143. expr = self.parse_python(value, 'eval')
  144. tag, value, body = self.parse(('else', 'elif', 'endif'))
  145. cond.tests.append((expr, body))
  146. continue
  147. break
  148. if value:
  149. self.fail('unexpected data after endif')
  150. return cond
  151. def parse_code(self, lines):
  152. margin = sys.maxint
  153. for line in lines[1:]:
  154. content = len(line.lstrip())
  155. if content:
  156. indent = len(line) - content
  157. margin = min(margin, indent)
  158. if lines:
  159. lines[0] = lines[0].lstrip()
  160. if margin < sys.maxint:
  161. for i in xrange(1, len(lines)):
  162. lines[i] = lines[i][margin:]
  163. while lines and not lines[-1]:
  164. lines.pop()
  165. while lines and not lines[0]:
  166. lines.pop(0)
  167. return self.parse_python('\n'.join(lines))
  168. def parse_data(self, text):
  169. start_lineno = lineno = self.lineno
  170. pos = 0
  171. end = len(text)
  172. nodes = []
  173. def match_or_fail(pos):
  174. match = token_re.match(text, pos)
  175. if match is None:
  176. self.fail('invalid syntax')
  177. return match.group().strip(), match.end()
  178. def write_expr(code):
  179. node = self.parse_python(code, 'eval')
  180. nodes.append(call_stmt('__to_unicode', [node], lineno))
  181. return code.count('\n')
  182. def write_data(value):
  183. if value:
  184. nodes.append(ast.Const(value, lineno=lineno))
  185. return value.count('\n')
  186. return 0
  187. while 1:
  188. offset = text.find('$', pos)
  189. if offset < 0:
  190. break
  191. next = text[offset + 1]
  192. if next == '{':
  193. lineno += write_data(text[pos:offset])
  194. pos = offset + 2
  195. level = 1
  196. while level:
  197. token, pos = match_or_fail(pos)
  198. if token in ('{', '}'):
  199. level += token == '{' and 1 or -1
  200. lineno += write_expr(text[offset + 2:pos - 1])
  201. elif next in namestart_chars:
  202. lineno += write_data(text[pos:offset])
  203. token, pos = match_or_fail(offset + 1)
  204. while pos < end:
  205. if text[pos] == '.' and pos + 1 < end and \
  206. text[pos + 1] in namestart_chars:
  207. token, pos = match_or_fail(pos + 1)
  208. elif text[pos] in '([':
  209. pos += 1
  210. level = 1
  211. while level:
  212. token, pos = match_or_fail(pos)
  213. if token in ('(', ')', '[', ']'):
  214. level += token in '([' and 1 or -1
  215. else:
  216. break
  217. lineno += write_expr(text[offset + 1:pos])
  218. else:
  219. lineno += write_data(text[pos:offset + 1])
  220. pos = offset + 1 + (next == '$')
  221. write_data(text[pos:])
  222. return ast.Discard(call_stmt(len(nodes) == 1 and '__write' or
  223. '__write_many', nodes, start_lineno),
  224. lineno=start_lineno)
  225. class Context(object):
  226. def __init__(self, namespace, charset, errors):
  227. self.charset = charset
  228. self.errors = errors
  229. self._namespace = namespace
  230. self._buffer = []
  231. self._write = self._buffer.append
  232. _extend = self._buffer.extend
  233. self.runtime = dict(
  234. Undefined=undefined,
  235. __to_unicode=self.to_unicode,
  236. __context=self,
  237. __write=self._write,
  238. __write_many=lambda *a: _extend(a)
  239. )
  240. def write(self, value):
  241. self._write(self.to_unicode(value))
  242. def to_unicode(self, value):
  243. if isinstance(value, str):
  244. return _decode_unicode(value, self.charset, self.errors)
  245. return unicode(value)
  246. def get_value(self, as_unicode=True):
  247. rv = u''.join(self._buffer)
  248. if not as_unicode:
  249. return rv.encode(self.charset, self.errors)
  250. return rv
  251. def __getitem__(self, key, default=undefined):
  252. try:
  253. return self._namespace[key]
  254. except KeyError:
  255. return getattr(builtins, key, default)
  256. def get(self, key, default=None):
  257. return self.__getitem__(key, default)
  258. def __setitem__(self, key, value):
  259. self._namespace[key] = value
  260. def __delitem__(self, key):
  261. del self._namespace[key]
  262. class TemplateCodeGenerator(ModuleCodeGenerator):
  263. def __init__(self, node, filename):
  264. ModuleCodeGenerator.__init__(self, transform(node, filename))
  265. def _nameOp(self, prefix, name):
  266. if name in runtime_vars:
  267. return self.emit(prefix + '_GLOBAL', name)
  268. return ModuleCodeGenerator._nameOp(self, prefix, name)
  269. class Template(object):
  270. """Represents a simple text based template. It's a good idea to load such
  271. templates from files on the file system to get better debug output.
  272. """
  273. default_context = {
  274. 'escape': utils.escape,
  275. 'url_quote': urls.url_quote,
  276. 'url_quote_plus': urls.url_quote_plus,
  277. 'url_encode': urls.url_encode
  278. }
  279. def __init__(self, source, filename='<template>', charset='utf-8',
  280. errors='strict', unicode_mode=True):
  281. if isinstance(source, str):
  282. source = _decode_unicode(source, charset, errors)
  283. if isinstance(filename, unicode):
  284. filename = filename.encode('utf-8')
  285. node = Parser(tokenize(u'\n'.join(source.splitlines()),
  286. filename), filename).parse()
  287. self.code = TemplateCodeGenerator(node, filename).getCode()
  288. self.filename = filename
  289. self.charset = charset
  290. self.errors = errors
  291. self.unicode_mode = unicode_mode
  292. @classmethod
  293. def from_file(cls, file, charset='utf-8', errors='strict',
  294. unicode_mode=True):
  295. """Load a template from a file.
  296. .. versionchanged:: 0.5
  297. The encoding parameter was renamed to charset.
  298. :param file: a filename or file object to load the template from.
  299. :param charset: the charset of the template to load.
  300. :param errors: the error behavior of the charset decoding.
  301. :param unicode_mode: set to `False` to disable unicode mode.
  302. :return: a template
  303. """
  304. close = False
  305. if isinstance(file, basestring):
  306. f = open(file, 'r')
  307. close = True
  308. try:
  309. data = _decode_unicode(f.read(), charset, errors)
  310. finally:
  311. if close:
  312. f.close()
  313. return cls(data, getattr(f, 'name', '<template>'), charset,
  314. errors, unicode_mode)
  315. def render(self, *args, **kwargs):
  316. """This function accepts either a dict or some keyword arguments which
  317. will then be the context the template is evaluated in. The return
  318. value will be the rendered template.
  319. :param context: the function accepts the same arguments as the
  320. :class:`dict` constructor.
  321. :return: the rendered template as string
  322. """
  323. ns = self.default_context.copy()
  324. if len(args) == 1 and isinstance(args[0], utils.MultiDict):
  325. ns.update(args[0].to_dict(flat=True))
  326. else:
  327. ns.update(dict(*args))
  328. if kwargs:
  329. ns.update(kwargs)
  330. context = Context(ns, self.charset, self.errors)
  331. exec self.code in context.runtime, context
  332. return context.get_value(self.unicode_mode)
  333. def substitute(self, *args, **kwargs):
  334. """For API compatibility with `string.Template`."""
  335. return self.render(*args, **kwargs)