PageRenderTime 27ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/globalsat/src/templates.py

http://gh615.googlecode.com/
Python | 514 lines | 478 code | 7 blank | 29 comment | 3 complexity | bd8e21eaf0794135451fdb63a4ef2974 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. r"""
  3. werkzeug.templates
  4. ~~~~~~~~~~~~~~~~~~
  5. This template engine recognizes ASP/PHP like blocks and executes the code
  6. in them::
  7. t = Template('<% for u in users %>${u["username"]}\n<% endfor %>')
  8. t.render(users=[{'username': 'John'},
  9. {'username': 'Jane'}])
  10. would result in::
  11. John
  12. Jane
  13. You can also create templates from files::
  14. t = Template.from_file('test.html')
  15. The syntax elements are a mixture of django, genshi text and mod_python
  16. templates and used internally in werkzeug components.
  17. We do not recommend using this template engine in a real environment
  18. because is quite slow and does not provide any advanced features. For
  19. simple applications (cgi script like) this can however be sufficient.
  20. Syntax Elements
  21. ---------------
  22. Printing Variables:
  23. .. sourcecode:: text
  24. $variable
  25. $variable.attribute[item](some, function)(calls)
  26. ${expression} or <%py print expression %>
  27. Keep in mind that the print statement adds a newline after the call or
  28. a whitespace if it ends with a comma.
  29. For Loops:
  30. .. sourcecode:: text
  31. <% for item in seq %>
  32. ...
  33. <% endfor %>
  34. While Loops:
  35. .. sourcecode:: text
  36. <% while expression %>
  37. <%py break / continue %>
  38. <% endwhile %>
  39. If Conditions:
  40. .. sourcecode:: text
  41. <% if expression %>
  42. ...
  43. <% elif expression %>
  44. ...
  45. <% else %>
  46. ...
  47. <% endif %>
  48. Python Expressions:
  49. .. sourcecode:: text
  50. <%py
  51. ...
  52. %>
  53. <%python
  54. ...
  55. %>
  56. Note on python expressions: You cannot start a loop in a python block
  57. and continue it in another one. This example does *not* work:
  58. .. sourcecode:: text
  59. <%python
  60. for item in seq:
  61. %>
  62. ...
  63. Comments:
  64. .. sourcecode:: text
  65. <%#
  66. This is a comment
  67. %>
  68. Missing Variables
  69. -----------------
  70. If you try to access a missing variable you will get back an `Undefined`
  71. object. You can iterate over such an object or print it and it won't
  72. fail. However every other operation will raise an error. To test if a
  73. variable is undefined you can use this expression:
  74. .. sourcecode:: text
  75. <% if variable is Undefined %>
  76. ...
  77. <% endif %>
  78. Python 2.3 Compatibility
  79. ------------------------
  80. Because of limitations in Python 2.3 it's impossible to achieve the
  81. semi-silent variable lookup fallback. If a template relies on undefined
  82. variables it won't execute under Python 2.3.
  83. :copyright: 2006-2008 by Armin Ronacher, Ka-Ping Yee.
  84. :license: BSD License.
  85. """
  86. import sys
  87. import re
  88. import __builtin__ as builtins
  89. from compiler import ast, parse
  90. from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
  91. from compiler.pycodegen import ModuleCodeGenerator
  92. from tokenize import PseudoToken
  93. #from werkzeug import utils
  94. #from werkzeug._internal import _decode_unicode
  95. from codecs import decode
  96. _decode_unicode = decode
  97. # Anything older than Python 2.4
  98. if sys.version_info < (2, 4):
  99. class AstMangler(object):
  100. def __getattr__(self, key):
  101. class_ = getattr(_ast, key)
  102. def wrapper(*args, **kw):
  103. lineno = kw.pop('lineno', None)
  104. obj = class_(*args, **kw)
  105. obj.lineno = lineno
  106. return obj
  107. return wrapper
  108. _ast = ast
  109. ast = AstMangler()
  110. # Copyright notice: The `parse_data` method uses the string interpolation
  111. # algorithm by Ka-Ping Yee which originally was part of `ltpl20.py`_
  112. #
  113. # .. _ltipl20.py: http://lfw.org/python/Itpl20.py
  114. token_re = re.compile('%s|%s(?s)' % (
  115. r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1',
  116. PseudoToken
  117. ))
  118. directive_re = re.compile(r'(?<!\\)<%(?:(#)|(py(?:thon)?\b)|'
  119. r'(?:\s*(\w+))\s*)(.*?)\s*%>\n?(?s)')
  120. escape_re = re.compile(r'\\\n|\\(\\|<%)')
  121. namestart_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
  122. undefined = type('UndefinedType', (object,), {
  123. '__iter__': lambda x: iter(()),
  124. '__repr__': lambda x: 'Undefined',
  125. '__str__': lambda x: ''
  126. })()
  127. runtime_vars = dict.fromkeys(('Undefined', '__to_unicode', '__context',
  128. '__write', '__write_many'))
  129. def call_stmt(func, args, lineno):
  130. return ast.CallFunc(ast.Name(func, lineno=lineno),
  131. args, lineno=lineno)
  132. def tokenize(source, filename):
  133. escape = escape_re.sub
  134. escape_repl = lambda m: m.group(1) or ''
  135. lineno = 1
  136. pos = 0
  137. for match in directive_re.finditer(source):
  138. start, end = match.span()
  139. if start > pos:
  140. data = source[pos:start]
  141. yield lineno, 'data', escape(escape_repl, data)
  142. lineno += data.count('\n')
  143. is_comment, is_code, cmd, args = match.groups()
  144. if is_code:
  145. yield lineno, 'code', args
  146. elif not is_comment:
  147. yield lineno, 'cmd', (cmd, args)
  148. lineno += source[start:end].count('\n')
  149. pos = end
  150. if pos < len(source):
  151. yield lineno, 'data', escape(escape_repl, source[pos:])
  152. def transform(node, filename):
  153. root = ast.Module(None, node, lineno=1)
  154. nodes = [root]
  155. while nodes:
  156. node = nodes.pop()
  157. node.filename = filename
  158. if node.__class__ in (ast.Printnl, ast.Print):
  159. node.dest = ast.Name('__context')
  160. elif node.__class__ is ast.Const and isinstance(node.value, str):
  161. try:
  162. node.value.decode('ascii')
  163. except UnicodeError:
  164. node.value = node.value.decode('utf-8')
  165. nodes.extend(node.getChildNodes())
  166. return root
  167. class TemplateSyntaxError(SyntaxError):
  168. def __init__(self, msg, filename, lineno):
  169. from linecache import getline
  170. l = getline(filename, lineno)
  171. SyntaxError.__init__(self, msg, (filename, lineno, len(l) or 1, l))
  172. class Parser(object):
  173. def __init__(self, gen, filename):
  174. self.gen = gen
  175. self.filename = filename
  176. self.lineno = 1
  177. def fail(self, msg):
  178. raise TemplateSyntaxError(msg, self.filename, self.lineno)
  179. def parse_python(self, expr, type='exec'):
  180. if isinstance(expr, unicode):
  181. expr = '\xef\xbb\xbf' + expr.encode('utf-8')
  182. try:
  183. node = parse(expr, type)
  184. except SyntaxError, e:
  185. raise TemplateSyntaxError(str(e), self.filename,
  186. self.lineno + e.lineno - 1)
  187. nodes = [node]
  188. while nodes:
  189. n = nodes.pop()
  190. if hasattr(n, 'lineno'):
  191. n.lineno = (n.lineno or 1) + self.lineno - 1
  192. nodes.extend(n.getChildNodes())
  193. return node.node
  194. def parse(self, needle=()):
  195. start_lineno = self.lineno
  196. result = []
  197. add = result.append
  198. for self.lineno, token, value in self.gen:
  199. if token == 'data':
  200. add(self.parse_data(value))
  201. elif token == 'code':
  202. add(self.parse_code(value.splitlines()))
  203. elif token == 'cmd':
  204. name, args = value
  205. if name in needle:
  206. return name, args, ast.Stmt(result, lineno=start_lineno)
  207. if name in ('for', 'while'):
  208. add(self.parse_loop(args, name))
  209. elif name == 'if':
  210. add(self.parse_if(args))
  211. else:
  212. self.fail('unknown directive %s' % name)
  213. if needle:
  214. self.fail('unexpected end of template')
  215. return ast.Stmt(result, lineno=start_lineno)
  216. def parse_loop(self, args, type):
  217. rv = self.parse_python('%s %s: pass' % (type, args), 'exec').nodes[0]
  218. tag, value, rv.body = self.parse(('end' + type, 'else'))
  219. if value:
  220. self.fail('unexpected data after ' + tag)
  221. if tag == 'else':
  222. tag, value, rv.else_ = self.parse(('end' + type,))
  223. if value:
  224. self.fail('unexpected data after else')
  225. return rv
  226. def parse_if(self, args):
  227. cond = self.parse_python('if %s: pass' % args).nodes[0]
  228. tag, value, body = self.parse(('else', 'elif', 'endif'))
  229. cond.tests[0] = (cond.tests[0][0], body)
  230. while 1:
  231. if tag == 'else':
  232. if value:
  233. self.fail('unexpected data after else')
  234. tag, value, cond.else_ = self.parse(('endif',))
  235. elif tag == 'elif':
  236. expr = self.parse_python(value, 'eval')
  237. tag, value, body = self.parse(('else', 'elif', 'endif'))
  238. cond.tests.append((expr, body))
  239. continue
  240. break
  241. if value:
  242. self.fail('unexpected data after endif')
  243. return cond
  244. def parse_code(self, lines):
  245. margin = sys.maxint
  246. for line in lines[1:]:
  247. content = len(line.lstrip())
  248. if content:
  249. indent = len(line) - content
  250. margin = min(margin, indent)
  251. if lines:
  252. lines[0] = lines[0].lstrip()
  253. if margin < sys.maxint:
  254. for i in xrange(1, len(lines)):
  255. lines[i] = lines[i][margin:]
  256. while lines and not lines[-1]:
  257. lines.pop()
  258. while lines and not lines[0]:
  259. lines.pop(0)
  260. return self.parse_python('\n'.join(lines))
  261. def parse_data(self, text):
  262. start_lineno = lineno = self.lineno
  263. pos = 0
  264. end = len(text)
  265. nodes = []
  266. def match_or_fail(pos):
  267. match = token_re.match(text, pos)
  268. if match is None:
  269. self.fail('invalid syntax')
  270. return match.group().strip(), match.end()
  271. def write_expr(code):
  272. node = self.parse_python(code, 'eval')
  273. nodes.append(call_stmt('__to_unicode', [node], lineno))
  274. return code.count('\n')
  275. def write_data(value):
  276. if value:
  277. nodes.append(ast.Const(value, lineno=lineno))
  278. return value.count('\n')
  279. return 0
  280. while 1:
  281. offset = text.find('$', pos)
  282. if offset < 0:
  283. break
  284. next = text[offset + 1]
  285. if next == '{':
  286. lineno += write_data(text[pos:offset])
  287. pos = offset + 2
  288. level = 1
  289. while level:
  290. token, pos = match_or_fail(pos)
  291. if token in ('{', '}'):
  292. level += token == '{' and 1 or -1
  293. lineno += write_expr(text[offset + 2:pos - 1])
  294. elif next in namestart_chars:
  295. lineno += write_data(text[pos:offset])
  296. token, pos = match_or_fail(offset + 1)
  297. while pos < end:
  298. if text[pos] == '.' and pos + 1 < end and \
  299. text[pos + 1] in namestart_chars:
  300. token, pos = match_or_fail(pos + 1)
  301. elif text[pos] in '([':
  302. pos += 1
  303. level = 1
  304. while level:
  305. token, pos = match_or_fail(pos)
  306. if token in ('(', ')', '[', ']'):
  307. level += token in '([' and 1 or -1
  308. else:
  309. break
  310. lineno += write_expr(text[offset + 1:pos])
  311. else:
  312. lineno += write_data(text[pos:offset + 1])
  313. pos = offset + 1 + (next == '$')
  314. write_data(text[pos:])
  315. return ast.Discard(call_stmt(len(nodes) == 1 and '__write' or
  316. '__write_many', nodes, start_lineno),
  317. lineno=start_lineno)
  318. class Context(object):
  319. def __init__(self, namespace, encoding, errors):
  320. self.encoding = encoding
  321. self.errors = errors
  322. self._namespace = namespace
  323. self._buffer = []
  324. self._write = self._buffer.append
  325. _extend = self._buffer.extend
  326. self.runtime = dict(
  327. Undefined=undefined,
  328. __to_unicode=self.to_unicode,
  329. __context=self,
  330. __write=self._write,
  331. __write_many=lambda *a: _extend(a)
  332. )
  333. def write(self, value):
  334. self._write(self.to_unicode(value))
  335. def to_unicode(self, value):
  336. if isinstance(value, str):
  337. return _decode_unicode(value, self.encoding, self.errors)
  338. return unicode(value)
  339. def get_value(self, as_unicode=True):
  340. rv = u''.join(self._buffer)
  341. if not as_unicode:
  342. return rv.encode(self.encoding, self.errors)
  343. return rv
  344. def __getitem__(self, key, default=undefined):
  345. try:
  346. return self._namespace[key]
  347. except KeyError:
  348. return getattr(builtins, key, default)
  349. def get(self, key, default=None):
  350. return self.__getitem__(key, default)
  351. def __setitem__(self, key, value):
  352. self._namespace[key] = value
  353. def __delitem__(self, key):
  354. del self._namespace[key]
  355. class TemplateCodeGenerator(ModuleCodeGenerator):
  356. def __init__(self, node, filename):
  357. ModuleCodeGenerator.__init__(self, transform(node, filename))
  358. def _nameOp(self, prefix, name):
  359. if name in runtime_vars:
  360. return self.emit(prefix + '_GLOBAL', name)
  361. return ModuleCodeGenerator._nameOp(self, prefix, name)
  362. class Template(object):
  363. """Represents a simple text based template. It's a good idea to load such
  364. templates from files on the file system to get better debug output.
  365. """
  366. default_context = {
  367. #'escape': utils.escape,
  368. #'url_quote': utils.url_quote,
  369. #'url_quote_plus': utils.url_quote_plus,
  370. #'url_encode': utils.url_encode
  371. }
  372. def __init__(self, source, filename='<template>', encoding='utf-8',
  373. errors='strict', unicode_mode=True):
  374. if isinstance(source, str):
  375. source = _decode_unicode(source, encoding, errors)
  376. if isinstance(filename, unicode):
  377. filename = filename.encode('utf-8')
  378. node = Parser(tokenize(u'\n'.join(source.splitlines()),
  379. filename), filename).parse()
  380. self.code = TemplateCodeGenerator(node, filename).getCode()
  381. self.filename = filename
  382. self.encoding = encoding
  383. self.errors = errors
  384. self.unicode_mode = unicode_mode
  385. def from_file(cls, file, encoding='utf-8', errors='strict',
  386. unicode_mode=True):
  387. """Load a template from a file."""
  388. close = False
  389. if isinstance(file, basestring):
  390. f = open(file, 'r')
  391. close = True
  392. try:
  393. data = _decode_unicode(f.read(), encoding, errors)
  394. finally:
  395. if close:
  396. f.close()
  397. return cls(data, getattr(f, 'name', '<template>'), encoding,
  398. errors, unicode_mode)
  399. from_file = classmethod(from_file)
  400. def render(self, *args, **kwargs):
  401. """This function accepts either a dict or some keyword arguments which
  402. will then be the context the template is evaluated in. The return
  403. value will be the rendered template.
  404. """
  405. ns = self.default_context.copy()
  406. ns.update(dict(*args, **kwargs))
  407. context = Context(ns, self.encoding, self.errors)
  408. if sys.version_info < (2, 4):
  409. exec self.code in context.runtime, ns
  410. else:
  411. exec self.code in context.runtime, context
  412. return context.get_value(self.unicode_mode)
  413. def substitute(self, *args, **kwargs):
  414. """For API compatibility with `string.Template`."""
  415. return self.render(*args, **kwargs)