1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/OpenSecurity/install/web.py-0.37/web/template.py Mon Dec 02 14:02:05 2013 +0100
1.3 @@ -0,0 +1,1515 @@
1.4 +"""
1.5 +simple, elegant templating
1.6 +(part of web.py)
1.7 +
1.8 +Template design:
1.9 +
1.10 +Template string is split into tokens and the tokens are combined into nodes.
1.11 +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and
1.12 +for-loop, if-loop etc are block nodes, which contain multiple child nodes.
1.13 +
1.14 +Each node can emit some python string. python string emitted by the
1.15 +root node is validated for safeeval and executed using python in the given environment.
1.16 +
1.17 +Enough care is taken to make sure the generated code and the template has line to line match,
1.18 +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.)
1.19 +
1.20 +Grammar:
1.21 +
1.22 + template -> defwith sections
1.23 + defwith -> '$def with (' arguments ')' | ''
1.24 + sections -> section*
1.25 + section -> block | assignment | line
1.26 +
1.27 + assignment -> '$ ' <assignment expression>
1.28 + line -> (text|expr)*
1.29 + text -> <any characters other than $>
1.30 + expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}'
1.31 + pyexpr -> <python expression>
1.32 +"""
1.33 +
1.34 +__all__ = [
1.35 + "Template",
1.36 + "Render", "render", "frender",
1.37 + "ParseError", "SecurityError",
1.38 + "test"
1.39 +]
1.40 +
1.41 +import tokenize
1.42 +import os
1.43 +import sys
1.44 +import glob
1.45 +import re
1.46 +from UserDict import DictMixin
1.47 +import warnings
1.48 +
1.49 +from utils import storage, safeunicode, safestr, re_compile
1.50 +from webapi import config
1.51 +from net import websafe
1.52 +
1.53 +def splitline(text):
1.54 + r"""
1.55 + Splits the given text at newline.
1.56 +
1.57 + >>> splitline('foo\nbar')
1.58 + ('foo\n', 'bar')
1.59 + >>> splitline('foo')
1.60 + ('foo', '')
1.61 + >>> splitline('')
1.62 + ('', '')
1.63 + """
1.64 + index = text.find('\n') + 1
1.65 + if index:
1.66 + return text[:index], text[index:]
1.67 + else:
1.68 + return text, ''
1.69 +
1.70 +class Parser:
1.71 + """Parser Base.
1.72 + """
1.73 + def __init__(self):
1.74 + self.statement_nodes = STATEMENT_NODES
1.75 + self.keywords = KEYWORDS
1.76 +
1.77 + def parse(self, text, name="<template>"):
1.78 + self.text = text
1.79 + self.name = name
1.80 +
1.81 + defwith, text = self.read_defwith(text)
1.82 + suite = self.read_suite(text)
1.83 + return DefwithNode(defwith, suite)
1.84 +
1.85 + def read_defwith(self, text):
1.86 + if text.startswith('$def with'):
1.87 + defwith, text = splitline(text)
1.88 + defwith = defwith[1:].strip() # strip $ and spaces
1.89 + return defwith, text
1.90 + else:
1.91 + return '', text
1.92 +
1.93 + def read_section(self, text):
1.94 + r"""Reads one section from the given text.
1.95 +
1.96 + section -> block | assignment | line
1.97 +
1.98 + >>> read_section = Parser().read_section
1.99 + >>> read_section('foo\nbar\n')
1.100 + (<line: [t'foo\n']>, 'bar\n')
1.101 + >>> read_section('$ a = b + 1\nfoo\n')
1.102 + (<assignment: 'a = b + 1'>, 'foo\n')
1.103 +
1.104 + read_section('$for in range(10):\n hello $i\nfoo)
1.105 + """
1.106 + if text.lstrip(' ').startswith('$'):
1.107 + index = text.index('$')
1.108 + begin_indent, text2 = text[:index], text[index+1:]
1.109 + ahead = self.python_lookahead(text2)
1.110 +
1.111 + if ahead == 'var':
1.112 + return self.read_var(text2)
1.113 + elif ahead in self.statement_nodes:
1.114 + return self.read_block_section(text2, begin_indent)
1.115 + elif ahead in self.keywords:
1.116 + return self.read_keyword(text2)
1.117 + elif ahead.strip() == '':
1.118 + # assignments starts with a space after $
1.119 + # ex: $ a = b + 2
1.120 + return self.read_assignment(text2)
1.121 + return self.readline(text)
1.122 +
1.123 + def read_var(self, text):
1.124 + r"""Reads a var statement.
1.125 +
1.126 + >>> read_var = Parser().read_var
1.127 + >>> read_var('var x=10\nfoo')
1.128 + (<var: x = 10>, 'foo')
1.129 + >>> read_var('var x: hello $name\nfoo')
1.130 + (<var: x = join_(u'hello ', escape_(name, True))>, 'foo')
1.131 + """
1.132 + line, text = splitline(text)
1.133 + tokens = self.python_tokens(line)
1.134 + if len(tokens) < 4:
1.135 + raise SyntaxError('Invalid var statement')
1.136 +
1.137 + name = tokens[1]
1.138 + sep = tokens[2]
1.139 + value = line.split(sep, 1)[1].strip()
1.140 +
1.141 + if sep == '=':
1.142 + pass # no need to process value
1.143 + elif sep == ':':
1.144 + #@@ Hack for backward-compatability
1.145 + if tokens[3] == '\n': # multi-line var statement
1.146 + block, text = self.read_indented_block(text, ' ')
1.147 + lines = [self.readline(x)[0] for x in block.splitlines()]
1.148 + nodes = []
1.149 + for x in lines:
1.150 + nodes.extend(x.nodes)
1.151 + nodes.append(TextNode('\n'))
1.152 + else: # single-line var statement
1.153 + linenode, _ = self.readline(value)
1.154 + nodes = linenode.nodes
1.155 + parts = [node.emit('') for node in nodes]
1.156 + value = "join_(%s)" % ", ".join(parts)
1.157 + else:
1.158 + raise SyntaxError('Invalid var statement')
1.159 + return VarNode(name, value), text
1.160 +
1.161 + def read_suite(self, text):
1.162 + r"""Reads section by section till end of text.
1.163 +
1.164 + >>> read_suite = Parser().read_suite
1.165 + >>> read_suite('hello $name\nfoo\n')
1.166 + [<line: [t'hello ', $name, t'\n']>, <line: [t'foo\n']>]
1.167 + """
1.168 + sections = []
1.169 + while text:
1.170 + section, text = self.read_section(text)
1.171 + sections.append(section)
1.172 + return SuiteNode(sections)
1.173 +
1.174 + def readline(self, text):
1.175 + r"""Reads one line from the text. Newline is supressed if the line ends with \.
1.176 +
1.177 + >>> readline = Parser().readline
1.178 + >>> readline('hello $name!\nbye!')
1.179 + (<line: [t'hello ', $name, t'!\n']>, 'bye!')
1.180 + >>> readline('hello $name!\\\nbye!')
1.181 + (<line: [t'hello ', $name, t'!']>, 'bye!')
1.182 + >>> readline('$f()\n\n')
1.183 + (<line: [$f(), t'\n']>, '\n')
1.184 + """
1.185 + line, text = splitline(text)
1.186 +
1.187 + # supress new line if line ends with \
1.188 + if line.endswith('\\\n'):
1.189 + line = line[:-2]
1.190 +
1.191 + nodes = []
1.192 + while line:
1.193 + node, line = self.read_node(line)
1.194 + nodes.append(node)
1.195 +
1.196 + return LineNode(nodes), text
1.197 +
1.198 + def read_node(self, text):
1.199 + r"""Reads a node from the given text and returns the node and remaining text.
1.200 +
1.201 + >>> read_node = Parser().read_node
1.202 + >>> read_node('hello $name')
1.203 + (t'hello ', '$name')
1.204 + >>> read_node('$name')
1.205 + ($name, '')
1.206 + """
1.207 + if text.startswith('$$'):
1.208 + return TextNode('$'), text[2:]
1.209 + elif text.startswith('$#'): # comment
1.210 + line, text = splitline(text)
1.211 + return TextNode('\n'), text
1.212 + elif text.startswith('$'):
1.213 + text = text[1:] # strip $
1.214 + if text.startswith(':'):
1.215 + escape = False
1.216 + text = text[1:] # strip :
1.217 + else:
1.218 + escape = True
1.219 + return self.read_expr(text, escape=escape)
1.220 + else:
1.221 + return self.read_text(text)
1.222 +
1.223 + def read_text(self, text):
1.224 + r"""Reads a text node from the given text.
1.225 +
1.226 + >>> read_text = Parser().read_text
1.227 + >>> read_text('hello $name')
1.228 + (t'hello ', '$name')
1.229 + """
1.230 + index = text.find('$')
1.231 + if index < 0:
1.232 + return TextNode(text), ''
1.233 + else:
1.234 + return TextNode(text[:index]), text[index:]
1.235 +
1.236 + def read_keyword(self, text):
1.237 + line, text = splitline(text)
1.238 + return StatementNode(line.strip() + "\n"), text
1.239 +
1.240 + def read_expr(self, text, escape=True):
1.241 + """Reads a python expression from the text and returns the expression and remaining text.
1.242 +
1.243 + expr -> simple_expr | paren_expr
1.244 + simple_expr -> id extended_expr
1.245 + extended_expr -> attr_access | paren_expr extended_expr | ''
1.246 + attr_access -> dot id extended_expr
1.247 + paren_expr -> [ tokens ] | ( tokens ) | { tokens }
1.248 +
1.249 + >>> read_expr = Parser().read_expr
1.250 + >>> read_expr("name")
1.251 + ($name, '')
1.252 + >>> read_expr("a.b and c")
1.253 + ($a.b, ' and c')
1.254 + >>> read_expr("a. b")
1.255 + ($a, '. b')
1.256 + >>> read_expr("name</h1>")
1.257 + ($name, '</h1>')
1.258 + >>> read_expr("(limit)ing")
1.259 + ($(limit), 'ing')
1.260 + >>> read_expr('a[1, 2][:3].f(1+2, "weird string[).", 3 + 4) done.')
1.261 + ($a[1, 2][:3].f(1+2, "weird string[).", 3 + 4), ' done.')
1.262 + """
1.263 + def simple_expr():
1.264 + identifier()
1.265 + extended_expr()
1.266 +
1.267 + def identifier():
1.268 + tokens.next()
1.269 +
1.270 + def extended_expr():
1.271 + lookahead = tokens.lookahead()
1.272 + if lookahead is None:
1.273 + return
1.274 + elif lookahead.value == '.':
1.275 + attr_access()
1.276 + elif lookahead.value in parens:
1.277 + paren_expr()
1.278 + extended_expr()
1.279 + else:
1.280 + return
1.281 +
1.282 + def attr_access():
1.283 + from token import NAME # python token constants
1.284 + dot = tokens.lookahead()
1.285 + if tokens.lookahead2().type == NAME:
1.286 + tokens.next() # consume dot
1.287 + identifier()
1.288 + extended_expr()
1.289 +
1.290 + def paren_expr():
1.291 + begin = tokens.next().value
1.292 + end = parens[begin]
1.293 + while True:
1.294 + if tokens.lookahead().value in parens:
1.295 + paren_expr()
1.296 + else:
1.297 + t = tokens.next()
1.298 + if t.value == end:
1.299 + break
1.300 + return
1.301 +
1.302 + parens = {
1.303 + "(": ")",
1.304 + "[": "]",
1.305 + "{": "}"
1.306 + }
1.307 +
1.308 + def get_tokens(text):
1.309 + """tokenize text using python tokenizer.
1.310 + Python tokenizer ignores spaces, but they might be important in some cases.
1.311 + This function introduces dummy space tokens when it identifies any ignored space.
1.312 + Each token is a storage object containing type, value, begin and end.
1.313 + """
1.314 + readline = iter([text]).next
1.315 + end = None
1.316 + for t in tokenize.generate_tokens(readline):
1.317 + t = storage(type=t[0], value=t[1], begin=t[2], end=t[3])
1.318 + if end is not None and end != t.begin:
1.319 + _, x1 = end
1.320 + _, x2 = t.begin
1.321 + yield storage(type=-1, value=text[x1:x2], begin=end, end=t.begin)
1.322 + end = t.end
1.323 + yield t
1.324 +
1.325 + class BetterIter:
1.326 + """Iterator like object with 2 support for 2 look aheads."""
1.327 + def __init__(self, items):
1.328 + self.iteritems = iter(items)
1.329 + self.items = []
1.330 + self.position = 0
1.331 + self.current_item = None
1.332 +
1.333 + def lookahead(self):
1.334 + if len(self.items) <= self.position:
1.335 + self.items.append(self._next())
1.336 + return self.items[self.position]
1.337 +
1.338 + def _next(self):
1.339 + try:
1.340 + return self.iteritems.next()
1.341 + except StopIteration:
1.342 + return None
1.343 +
1.344 + def lookahead2(self):
1.345 + if len(self.items) <= self.position+1:
1.346 + self.items.append(self._next())
1.347 + return self.items[self.position+1]
1.348 +
1.349 + def next(self):
1.350 + self.current_item = self.lookahead()
1.351 + self.position += 1
1.352 + return self.current_item
1.353 +
1.354 + tokens = BetterIter(get_tokens(text))
1.355 +
1.356 + if tokens.lookahead().value in parens:
1.357 + paren_expr()
1.358 + else:
1.359 + simple_expr()
1.360 + row, col = tokens.current_item.end
1.361 + return ExpressionNode(text[:col], escape=escape), text[col:]
1.362 +
1.363 + def read_assignment(self, text):
1.364 + r"""Reads assignment statement from text.
1.365 +
1.366 + >>> read_assignment = Parser().read_assignment
1.367 + >>> read_assignment('a = b + 1\nfoo')
1.368 + (<assignment: 'a = b + 1'>, 'foo')
1.369 + """
1.370 + line, text = splitline(text)
1.371 + return AssignmentNode(line.strip()), text
1.372 +
1.373 + def python_lookahead(self, text):
1.374 + """Returns the first python token from the given text.
1.375 +
1.376 + >>> python_lookahead = Parser().python_lookahead
1.377 + >>> python_lookahead('for i in range(10):')
1.378 + 'for'
1.379 + >>> python_lookahead('else:')
1.380 + 'else'
1.381 + >>> python_lookahead(' x = 1')
1.382 + ' '
1.383 + """
1.384 + readline = iter([text]).next
1.385 + tokens = tokenize.generate_tokens(readline)
1.386 + return tokens.next()[1]
1.387 +
1.388 + def python_tokens(self, text):
1.389 + readline = iter([text]).next
1.390 + tokens = tokenize.generate_tokens(readline)
1.391 + return [t[1] for t in tokens]
1.392 +
1.393 + def read_indented_block(self, text, indent):
1.394 + r"""Read a block of text. A block is what typically follows a for or it statement.
1.395 + It can be in the same line as that of the statement or an indented block.
1.396 +
1.397 + >>> read_indented_block = Parser().read_indented_block
1.398 + >>> read_indented_block(' a\n b\nc', ' ')
1.399 + ('a\nb\n', 'c')
1.400 + >>> read_indented_block(' a\n b\n c\nd', ' ')
1.401 + ('a\n b\nc\n', 'd')
1.402 + >>> read_indented_block(' a\n\n b\nc', ' ')
1.403 + ('a\n\n b\n', 'c')
1.404 + """
1.405 + if indent == '':
1.406 + return '', text
1.407 +
1.408 + block = ""
1.409 + while text:
1.410 + line, text2 = splitline(text)
1.411 + if line.strip() == "":
1.412 + block += '\n'
1.413 + elif line.startswith(indent):
1.414 + block += line[len(indent):]
1.415 + else:
1.416 + break
1.417 + text = text2
1.418 + return block, text
1.419 +
1.420 + def read_statement(self, text):
1.421 + r"""Reads a python statement.
1.422 +
1.423 + >>> read_statement = Parser().read_statement
1.424 + >>> read_statement('for i in range(10): hello $name')
1.425 + ('for i in range(10):', ' hello $name')
1.426 + """
1.427 + tok = PythonTokenizer(text)
1.428 + tok.consume_till(':')
1.429 + return text[:tok.index], text[tok.index:]
1.430 +
1.431 + def read_block_section(self, text, begin_indent=''):
1.432 + r"""
1.433 + >>> read_block_section = Parser().read_block_section
1.434 + >>> read_block_section('for i in range(10): hello $i\nfoo')
1.435 + (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
1.436 + >>> read_block_section('for i in range(10):\n hello $i\n foo', begin_indent=' ')
1.437 + (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, ' foo')
1.438 + >>> read_block_section('for i in range(10):\n hello $i\nfoo')
1.439 + (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
1.440 + """
1.441 + line, text = splitline(text)
1.442 + stmt, line = self.read_statement(line)
1.443 + keyword = self.python_lookahead(stmt)
1.444 +
1.445 + # if there is some thing left in the line
1.446 + if line.strip():
1.447 + block = line.lstrip()
1.448 + else:
1.449 + def find_indent(text):
1.450 + rx = re_compile(' +')
1.451 + match = rx.match(text)
1.452 + first_indent = match and match.group(0)
1.453 + return first_indent or ""
1.454 +
1.455 + # find the indentation of the block by looking at the first line
1.456 + first_indent = find_indent(text)[len(begin_indent):]
1.457 +
1.458 + #TODO: fix this special case
1.459 + if keyword == "code":
1.460 + indent = begin_indent + first_indent
1.461 + else:
1.462 + indent = begin_indent + min(first_indent, INDENT)
1.463 +
1.464 + block, text = self.read_indented_block(text, indent)
1.465 +
1.466 + return self.create_block_node(keyword, stmt, block, begin_indent), text
1.467 +
1.468 + def create_block_node(self, keyword, stmt, block, begin_indent):
1.469 + if keyword in self.statement_nodes:
1.470 + return self.statement_nodes[keyword](stmt, block, begin_indent)
1.471 + else:
1.472 + raise ParseError, 'Unknown statement: %s' % repr(keyword)
1.473 +
1.474 +class PythonTokenizer:
1.475 + """Utility wrapper over python tokenizer."""
1.476 + def __init__(self, text):
1.477 + self.text = text
1.478 + readline = iter([text]).next
1.479 + self.tokens = tokenize.generate_tokens(readline)
1.480 + self.index = 0
1.481 +
1.482 + def consume_till(self, delim):
1.483 + """Consumes tokens till colon.
1.484 +
1.485 + >>> tok = PythonTokenizer('for i in range(10): hello $i')
1.486 + >>> tok.consume_till(':')
1.487 + >>> tok.text[:tok.index]
1.488 + 'for i in range(10):'
1.489 + >>> tok.text[tok.index:]
1.490 + ' hello $i'
1.491 + """
1.492 + try:
1.493 + while True:
1.494 + t = self.next()
1.495 + if t.value == delim:
1.496 + break
1.497 + elif t.value == '(':
1.498 + self.consume_till(')')
1.499 + elif t.value == '[':
1.500 + self.consume_till(']')
1.501 + elif t.value == '{':
1.502 + self.consume_till('}')
1.503 +
1.504 + # if end of line is found, it is an exception.
1.505 + # Since there is no easy way to report the line number,
1.506 + # leave the error reporting to the python parser later
1.507 + #@@ This should be fixed.
1.508 + if t.value == '\n':
1.509 + break
1.510 + except:
1.511 + #raise ParseError, "Expected %s, found end of line." % repr(delim)
1.512 +
1.513 + # raising ParseError doesn't show the line number.
1.514 + # if this error is ignored, then it will be caught when compiling the python code.
1.515 + return
1.516 +
1.517 + def next(self):
1.518 + type, t, begin, end, line = self.tokens.next()
1.519 + row, col = end
1.520 + self.index = col
1.521 + return storage(type=type, value=t, begin=begin, end=end)
1.522 +
1.523 +class DefwithNode:
1.524 + def __init__(self, defwith, suite):
1.525 + if defwith:
1.526 + self.defwith = defwith.replace('with', '__template__') + ':'
1.527 + # offset 4 lines. for encoding, __lineoffset__, loop and self.
1.528 + self.defwith += "\n __lineoffset__ = -4"
1.529 + else:
1.530 + self.defwith = 'def __template__():'
1.531 + # offset 4 lines for encoding, __template__, __lineoffset__, loop and self.
1.532 + self.defwith += "\n __lineoffset__ = -5"
1.533 +
1.534 + self.defwith += "\n loop = ForLoop()"
1.535 + self.defwith += "\n self = TemplateResult(); extend_ = self.extend"
1.536 + self.suite = suite
1.537 + self.end = "\n return self"
1.538 +
1.539 + def emit(self, indent):
1.540 + encoding = "# coding: utf-8\n"
1.541 + return encoding + self.defwith + self.suite.emit(indent + INDENT) + self.end
1.542 +
1.543 + def __repr__(self):
1.544 + return "<defwith: %s, %s>" % (self.defwith, self.suite)
1.545 +
1.546 +class TextNode:
1.547 + def __init__(self, value):
1.548 + self.value = value
1.549 +
1.550 + def emit(self, indent, begin_indent=''):
1.551 + return repr(safeunicode(self.value))
1.552 +
1.553 + def __repr__(self):
1.554 + return 't' + repr(self.value)
1.555 +
1.556 +class ExpressionNode:
1.557 + def __init__(self, value, escape=True):
1.558 + self.value = value.strip()
1.559 +
1.560 + # convert ${...} to $(...)
1.561 + if value.startswith('{') and value.endswith('}'):
1.562 + self.value = '(' + self.value[1:-1] + ')'
1.563 +
1.564 + self.escape = escape
1.565 +
1.566 + def emit(self, indent, begin_indent=''):
1.567 + return 'escape_(%s, %s)' % (self.value, bool(self.escape))
1.568 +
1.569 + def __repr__(self):
1.570 + if self.escape:
1.571 + escape = ''
1.572 + else:
1.573 + escape = ':'
1.574 + return "$%s%s" % (escape, self.value)
1.575 +
1.576 +class AssignmentNode:
1.577 + def __init__(self, code):
1.578 + self.code = code
1.579 +
1.580 + def emit(self, indent, begin_indent=''):
1.581 + return indent + self.code + "\n"
1.582 +
1.583 + def __repr__(self):
1.584 + return "<assignment: %s>" % repr(self.code)
1.585 +
1.586 +class LineNode:
1.587 + def __init__(self, nodes):
1.588 + self.nodes = nodes
1.589 +
1.590 + def emit(self, indent, text_indent='', name=''):
1.591 + text = [node.emit('') for node in self.nodes]
1.592 + if text_indent:
1.593 + text = [repr(text_indent)] + text
1.594 +
1.595 + return indent + "extend_([%s])\n" % ", ".join(text)
1.596 +
1.597 + def __repr__(self):
1.598 + return "<line: %s>" % repr(self.nodes)
1.599 +
1.600 +INDENT = ' ' # 4 spaces
1.601 +
1.602 +class BlockNode:
1.603 + def __init__(self, stmt, block, begin_indent=''):
1.604 + self.stmt = stmt
1.605 + self.suite = Parser().read_suite(block)
1.606 + self.begin_indent = begin_indent
1.607 +
1.608 + def emit(self, indent, text_indent=''):
1.609 + text_indent = self.begin_indent + text_indent
1.610 + out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
1.611 + return out
1.612 +
1.613 + def __repr__(self):
1.614 + return "<block: %s, %s>" % (repr(self.stmt), repr(self.suite))
1.615 +
1.616 +class ForNode(BlockNode):
1.617 + def __init__(self, stmt, block, begin_indent=''):
1.618 + self.original_stmt = stmt
1.619 + tok = PythonTokenizer(stmt)
1.620 + tok.consume_till('in')
1.621 + a = stmt[:tok.index] # for i in
1.622 + b = stmt[tok.index:-1] # rest of for stmt excluding :
1.623 + stmt = a + ' loop.setup(' + b.strip() + '):'
1.624 + BlockNode.__init__(self, stmt, block, begin_indent)
1.625 +
1.626 + def __repr__(self):
1.627 + return "<block: %s, %s>" % (repr(self.original_stmt), repr(self.suite))
1.628 +
1.629 +class CodeNode:
1.630 + def __init__(self, stmt, block, begin_indent=''):
1.631 + # compensate one line for $code:
1.632 + self.code = "\n" + block
1.633 +
1.634 + def emit(self, indent, text_indent=''):
1.635 + import re
1.636 + rx = re.compile('^', re.M)
1.637 + return rx.sub(indent, self.code).rstrip(' ')
1.638 +
1.639 + def __repr__(self):
1.640 + return "<code: %s>" % repr(self.code)
1.641 +
1.642 +class StatementNode:
1.643 + def __init__(self, stmt):
1.644 + self.stmt = stmt
1.645 +
1.646 + def emit(self, indent, begin_indent=''):
1.647 + return indent + self.stmt
1.648 +
1.649 + def __repr__(self):
1.650 + return "<stmt: %s>" % repr(self.stmt)
1.651 +
1.652 +class IfNode(BlockNode):
1.653 + pass
1.654 +
1.655 +class ElseNode(BlockNode):
1.656 + pass
1.657 +
1.658 +class ElifNode(BlockNode):
1.659 + pass
1.660 +
1.661 +class DefNode(BlockNode):
1.662 + def __init__(self, *a, **kw):
1.663 + BlockNode.__init__(self, *a, **kw)
1.664 +
1.665 + code = CodeNode("", "")
1.666 + code.code = "self = TemplateResult(); extend_ = self.extend\n"
1.667 + self.suite.sections.insert(0, code)
1.668 +
1.669 + code = CodeNode("", "")
1.670 + code.code = "return self\n"
1.671 + self.suite.sections.append(code)
1.672 +
1.673 + def emit(self, indent, text_indent=''):
1.674 + text_indent = self.begin_indent + text_indent
1.675 + out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
1.676 + return indent + "__lineoffset__ -= 3\n" + out
1.677 +
1.678 +class VarNode:
1.679 + def __init__(self, name, value):
1.680 + self.name = name
1.681 + self.value = value
1.682 +
1.683 + def emit(self, indent, text_indent):
1.684 + return indent + "self[%s] = %s\n" % (repr(self.name), self.value)
1.685 +
1.686 + def __repr__(self):
1.687 + return "<var: %s = %s>" % (self.name, self.value)
1.688 +
1.689 +class SuiteNode:
1.690 + """Suite is a list of sections."""
1.691 + def __init__(self, sections):
1.692 + self.sections = sections
1.693 +
1.694 + def emit(self, indent, text_indent=''):
1.695 + return "\n" + "".join([s.emit(indent, text_indent) for s in self.sections])
1.696 +
1.697 + def __repr__(self):
1.698 + return repr(self.sections)
1.699 +
1.700 +STATEMENT_NODES = {
1.701 + 'for': ForNode,
1.702 + 'while': BlockNode,
1.703 + 'if': IfNode,
1.704 + 'elif': ElifNode,
1.705 + 'else': ElseNode,
1.706 + 'def': DefNode,
1.707 + 'code': CodeNode
1.708 +}
1.709 +
1.710 +KEYWORDS = [
1.711 + "pass",
1.712 + "break",
1.713 + "continue",
1.714 + "return"
1.715 +]
1.716 +
1.717 +TEMPLATE_BUILTIN_NAMES = [
1.718 + "dict", "enumerate", "float", "int", "bool", "list", "long", "reversed",
1.719 + "set", "slice", "tuple", "xrange",
1.720 + "abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex",
1.721 + "id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range",
1.722 + "True", "False",
1.723 + "None",
1.724 + "__import__", # some c-libraries like datetime requires __import__ to present in the namespace
1.725 +]
1.726 +
1.727 +import __builtin__
1.728 +TEMPLATE_BUILTINS = dict([(name, getattr(__builtin__, name)) for name in TEMPLATE_BUILTIN_NAMES if name in __builtin__.__dict__])
1.729 +
1.730 +class ForLoop:
1.731 + """
1.732 + Wrapper for expression in for stament to support loop.xxx helpers.
1.733 +
1.734 + >>> loop = ForLoop()
1.735 + >>> for x in loop.setup(['a', 'b', 'c']):
1.736 + ... print loop.index, loop.revindex, loop.parity, x
1.737 + ...
1.738 + 1 3 odd a
1.739 + 2 2 even b
1.740 + 3 1 odd c
1.741 + >>> loop.index
1.742 + Traceback (most recent call last):
1.743 + ...
1.744 + AttributeError: index
1.745 + """
1.746 + def __init__(self):
1.747 + self._ctx = None
1.748 +
1.749 + def __getattr__(self, name):
1.750 + if self._ctx is None:
1.751 + raise AttributeError, name
1.752 + else:
1.753 + return getattr(self._ctx, name)
1.754 +
1.755 + def setup(self, seq):
1.756 + self._push()
1.757 + return self._ctx.setup(seq)
1.758 +
1.759 + def _push(self):
1.760 + self._ctx = ForLoopContext(self, self._ctx)
1.761 +
1.762 + def _pop(self):
1.763 + self._ctx = self._ctx.parent
1.764 +
1.765 +class ForLoopContext:
1.766 + """Stackable context for ForLoop to support nested for loops.
1.767 + """
1.768 + def __init__(self, forloop, parent):
1.769 + self._forloop = forloop
1.770 + self.parent = parent
1.771 +
1.772 + def setup(self, seq):
1.773 + try:
1.774 + self.length = len(seq)
1.775 + except:
1.776 + self.length = 0
1.777 +
1.778 + self.index = 0
1.779 + for a in seq:
1.780 + self.index += 1
1.781 + yield a
1.782 + self._forloop._pop()
1.783 +
1.784 + index0 = property(lambda self: self.index-1)
1.785 + first = property(lambda self: self.index == 1)
1.786 + last = property(lambda self: self.index == self.length)
1.787 + odd = property(lambda self: self.index % 2 == 1)
1.788 + even = property(lambda self: self.index % 2 == 0)
1.789 + parity = property(lambda self: ['odd', 'even'][self.even])
1.790 + revindex0 = property(lambda self: self.length - self.index)
1.791 + revindex = property(lambda self: self.length - self.index + 1)
1.792 +
1.793 +class BaseTemplate:
1.794 + def __init__(self, code, filename, filter, globals, builtins):
1.795 + self.filename = filename
1.796 + self.filter = filter
1.797 + self._globals = globals
1.798 + self._builtins = builtins
1.799 + if code:
1.800 + self.t = self._compile(code)
1.801 + else:
1.802 + self.t = lambda: ''
1.803 +
1.804 + def _compile(self, code):
1.805 + env = self.make_env(self._globals or {}, self._builtins)
1.806 + exec(code, env)
1.807 + return env['__template__']
1.808 +
1.809 + def __call__(self, *a, **kw):
1.810 + __hidetraceback__ = True
1.811 + return self.t(*a, **kw)
1.812 +
1.813 + def make_env(self, globals, builtins):
1.814 + return dict(globals,
1.815 + __builtins__=builtins,
1.816 + ForLoop=ForLoop,
1.817 + TemplateResult=TemplateResult,
1.818 + escape_=self._escape,
1.819 + join_=self._join
1.820 + )
1.821 + def _join(self, *items):
1.822 + return u"".join(items)
1.823 +
1.824 + def _escape(self, value, escape=False):
1.825 + if value is None:
1.826 + value = ''
1.827 +
1.828 + value = safeunicode(value)
1.829 + if escape and self.filter:
1.830 + value = self.filter(value)
1.831 + return value
1.832 +
1.833 +class Template(BaseTemplate):
1.834 + CONTENT_TYPES = {
1.835 + '.html' : 'text/html; charset=utf-8',
1.836 + '.xhtml' : 'application/xhtml+xml; charset=utf-8',
1.837 + '.txt' : 'text/plain',
1.838 + }
1.839 + FILTERS = {
1.840 + '.html': websafe,
1.841 + '.xhtml': websafe,
1.842 + '.xml': websafe
1.843 + }
1.844 + globals = {}
1.845 +
1.846 + def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None, extensions=None):
1.847 + self.extensions = extensions or []
1.848 + text = Template.normalize_text(text)
1.849 + code = self.compile_template(text, filename)
1.850 +
1.851 + _, ext = os.path.splitext(filename)
1.852 + filter = filter or self.FILTERS.get(ext, None)
1.853 + self.content_type = self.CONTENT_TYPES.get(ext, None)
1.854 +
1.855 + if globals is None:
1.856 + globals = self.globals
1.857 + if builtins is None:
1.858 + builtins = TEMPLATE_BUILTINS
1.859 +
1.860 + BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
1.861 +
1.862 + def normalize_text(text):
1.863 + """Normalizes template text by correcting \r\n, tabs and BOM chars."""
1.864 + text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
1.865 + if not text.endswith('\n'):
1.866 + text += '\n'
1.867 +
1.868 + # ignore BOM chars at the begining of template
1.869 + BOM = '\xef\xbb\xbf'
1.870 + if isinstance(text, str) and text.startswith(BOM):
1.871 + text = text[len(BOM):]
1.872 +
1.873 + # support fort \$ for backward-compatibility
1.874 + text = text.replace(r'\$', '$$')
1.875 + return text
1.876 + normalize_text = staticmethod(normalize_text)
1.877 +
1.878 + def __call__(self, *a, **kw):
1.879 + __hidetraceback__ = True
1.880 + import webapi as web
1.881 + if 'headers' in web.ctx and self.content_type:
1.882 + web.header('Content-Type', self.content_type, unique=True)
1.883 +
1.884 + return BaseTemplate.__call__(self, *a, **kw)
1.885 +
1.886 + def generate_code(text, filename, parser=None):
1.887 + # parse the text
1.888 + parser = parser or Parser()
1.889 + rootnode = parser.parse(text, filename)
1.890 +
1.891 + # generate python code from the parse tree
1.892 + code = rootnode.emit(indent="").strip()
1.893 + return safestr(code)
1.894 +
1.895 + generate_code = staticmethod(generate_code)
1.896 +
1.897 + def create_parser(self):
1.898 + p = Parser()
1.899 + for ext in self.extensions:
1.900 + p = ext(p)
1.901 + return p
1.902 +
1.903 + def compile_template(self, template_string, filename):
1.904 + code = Template.generate_code(template_string, filename, parser=self.create_parser())
1.905 +
1.906 + def get_source_line(filename, lineno):
1.907 + try:
1.908 + lines = open(filename).read().splitlines()
1.909 + return lines[lineno]
1.910 + except:
1.911 + return None
1.912 +
1.913 + try:
1.914 + # compile the code first to report the errors, if any, with the filename
1.915 + compiled_code = compile(code, filename, 'exec')
1.916 + except SyntaxError, e:
1.917 + # display template line that caused the error along with the traceback.
1.918 + try:
1.919 + e.msg += '\n\nTemplate traceback:\n File %s, line %s\n %s' % \
1.920 + (repr(e.filename), e.lineno, get_source_line(e.filename, e.lineno-1))
1.921 + except:
1.922 + pass
1.923 + raise
1.924 +
1.925 + # make sure code is safe - but not with jython, it doesn't have a working compiler module
1.926 + if not sys.platform.startswith('java'):
1.927 + try:
1.928 + import compiler
1.929 + ast = compiler.parse(code)
1.930 + SafeVisitor().walk(ast, filename)
1.931 + except ImportError:
1.932 + warnings.warn("Unabled to import compiler module. Unable to check templates for safety.")
1.933 + else:
1.934 + warnings.warn("SECURITY ISSUE: You are using Jython, which does not support checking templates for safety. Your templates can execute arbitrary code.")
1.935 +
1.936 + return compiled_code
1.937 +
1.938 +class CompiledTemplate(Template):
1.939 + def __init__(self, f, filename):
1.940 + Template.__init__(self, '', filename)
1.941 + self.t = f
1.942 +
1.943 + def compile_template(self, *a):
1.944 + return None
1.945 +
1.946 + def _compile(self, *a):
1.947 + return None
1.948 +
1.949 +class Render:
1.950 + """The most preferred way of using templates.
1.951 +
1.952 + render = web.template.render('templates')
1.953 + print render.foo()
1.954 +
1.955 + Optional parameter can be `base` can be used to pass output of
1.956 + every template through the base template.
1.957 +
1.958 + render = web.template.render('templates', base='layout')
1.959 + """
1.960 + def __init__(self, loc='templates', cache=None, base=None, **keywords):
1.961 + self._loc = loc
1.962 + self._keywords = keywords
1.963 +
1.964 + if cache is None:
1.965 + cache = not config.get('debug', False)
1.966 +
1.967 + if cache:
1.968 + self._cache = {}
1.969 + else:
1.970 + self._cache = None
1.971 +
1.972 + if base and not hasattr(base, '__call__'):
1.973 + # make base a function, so that it can be passed to sub-renders
1.974 + self._base = lambda page: self._template(base)(page)
1.975 + else:
1.976 + self._base = base
1.977 +
1.978 + def _add_global(self, obj, name=None):
1.979 + """Add a global to this rendering instance."""
1.980 + if 'globals' not in self._keywords: self._keywords['globals'] = {}
1.981 + if not name:
1.982 + name = obj.__name__
1.983 + self._keywords['globals'][name] = obj
1.984 +
1.985 + def _lookup(self, name):
1.986 + path = os.path.join(self._loc, name)
1.987 + if os.path.isdir(path):
1.988 + return 'dir', path
1.989 + else:
1.990 + path = self._findfile(path)
1.991 + if path:
1.992 + return 'file', path
1.993 + else:
1.994 + return 'none', None
1.995 +
1.996 + def _load_template(self, name):
1.997 + kind, path = self._lookup(name)
1.998 +
1.999 + if kind == 'dir':
1.1000 + return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
1.1001 + elif kind == 'file':
1.1002 + return Template(open(path).read(), filename=path, **self._keywords)
1.1003 + else:
1.1004 + raise AttributeError, "No template named " + name
1.1005 +
1.1006 + def _findfile(self, path_prefix):
1.1007 + p = [f for f in glob.glob(path_prefix + '.*') if not f.endswith('~')] # skip backup files
1.1008 + p.sort() # sort the matches for deterministic order
1.1009 + return p and p[0]
1.1010 +
1.1011 + def _template(self, name):
1.1012 + if self._cache is not None:
1.1013 + if name not in self._cache:
1.1014 + self._cache[name] = self._load_template(name)
1.1015 + return self._cache[name]
1.1016 + else:
1.1017 + return self._load_template(name)
1.1018 +
1.1019 + def __getattr__(self, name):
1.1020 + t = self._template(name)
1.1021 + if self._base and isinstance(t, Template):
1.1022 + def template(*a, **kw):
1.1023 + return self._base(t(*a, **kw))
1.1024 + return template
1.1025 + else:
1.1026 + return self._template(name)
1.1027 +
1.1028 +class GAE_Render(Render):
1.1029 + # Render gets over-written. make a copy here.
1.1030 + super = Render
1.1031 + def __init__(self, loc, *a, **kw):
1.1032 + GAE_Render.super.__init__(self, loc, *a, **kw)
1.1033 +
1.1034 + import types
1.1035 + if isinstance(loc, types.ModuleType):
1.1036 + self.mod = loc
1.1037 + else:
1.1038 + name = loc.rstrip('/').replace('/', '.')
1.1039 + self.mod = __import__(name, None, None, ['x'])
1.1040 +
1.1041 + self.mod.__dict__.update(kw.get('builtins', TEMPLATE_BUILTINS))
1.1042 + self.mod.__dict__.update(Template.globals)
1.1043 + self.mod.__dict__.update(kw.get('globals', {}))
1.1044 +
1.1045 + def _load_template(self, name):
1.1046 + t = getattr(self.mod, name)
1.1047 + import types
1.1048 + if isinstance(t, types.ModuleType):
1.1049 + return GAE_Render(t, cache=self._cache is not None, base=self._base, **self._keywords)
1.1050 + else:
1.1051 + return t
1.1052 +
1.1053 +render = Render
1.1054 +# setup render for Google App Engine.
1.1055 +try:
1.1056 + from google import appengine
1.1057 + render = Render = GAE_Render
1.1058 +except ImportError:
1.1059 + pass
1.1060 +
1.1061 +def frender(path, **keywords):
1.1062 + """Creates a template from the given file path.
1.1063 + """
1.1064 + return Template(open(path).read(), filename=path, **keywords)
1.1065 +
1.1066 +def compile_templates(root):
1.1067 + """Compiles templates to python code."""
1.1068 + re_start = re_compile('^', re.M)
1.1069 +
1.1070 + for dirpath, dirnames, filenames in os.walk(root):
1.1071 + filenames = [f for f in filenames if not f.startswith('.') and not f.endswith('~') and not f.startswith('__init__.py')]
1.1072 +
1.1073 + for d in dirnames[:]:
1.1074 + if d.startswith('.'):
1.1075 + dirnames.remove(d) # don't visit this dir
1.1076 +
1.1077 + out = open(os.path.join(dirpath, '__init__.py'), 'w')
1.1078 + out.write('from web.template import CompiledTemplate, ForLoop, TemplateResult\n\n')
1.1079 + if dirnames:
1.1080 + out.write("import " + ", ".join(dirnames))
1.1081 + out.write("\n")
1.1082 +
1.1083 + for f in filenames:
1.1084 + path = os.path.join(dirpath, f)
1.1085 +
1.1086 + if '.' in f:
1.1087 + name, _ = f.split('.', 1)
1.1088 + else:
1.1089 + name = f
1.1090 +
1.1091 + text = open(path).read()
1.1092 + text = Template.normalize_text(text)
1.1093 + code = Template.generate_code(text, path)
1.1094 +
1.1095 + code = code.replace("__template__", name, 1)
1.1096 +
1.1097 + out.write(code)
1.1098 +
1.1099 + out.write('\n\n')
1.1100 + out.write('%s = CompiledTemplate(%s, %s)\n' % (name, name, repr(path)))
1.1101 + out.write("join_ = %s._join; escape_ = %s._escape\n\n" % (name, name))
1.1102 +
1.1103 + # create template to make sure it compiles
1.1104 + t = Template(open(path).read(), path)
1.1105 + out.close()
1.1106 +
1.1107 +class ParseError(Exception):
1.1108 + pass
1.1109 +
1.1110 +class SecurityError(Exception):
1.1111 + """The template seems to be trying to do something naughty."""
1.1112 + pass
1.1113 +
1.1114 +# Enumerate all the allowed AST nodes
1.1115 +ALLOWED_AST_NODES = [
1.1116 + "Add", "And",
1.1117 +# "AssAttr",
1.1118 + "AssList", "AssName", "AssTuple",
1.1119 +# "Assert",
1.1120 + "Assign", "AugAssign",
1.1121 +# "Backquote",
1.1122 + "Bitand", "Bitor", "Bitxor", "Break",
1.1123 + "CallFunc","Class", "Compare", "Const", "Continue",
1.1124 + "Decorators", "Dict", "Discard", "Div",
1.1125 + "Ellipsis", "EmptyNode",
1.1126 +# "Exec",
1.1127 + "Expression", "FloorDiv", "For",
1.1128 +# "From",
1.1129 + "Function",
1.1130 + "GenExpr", "GenExprFor", "GenExprIf", "GenExprInner",
1.1131 + "Getattr",
1.1132 +# "Global",
1.1133 + "If", "IfExp",
1.1134 +# "Import",
1.1135 + "Invert", "Keyword", "Lambda", "LeftShift",
1.1136 + "List", "ListComp", "ListCompFor", "ListCompIf", "Mod",
1.1137 + "Module",
1.1138 + "Mul", "Name", "Not", "Or", "Pass", "Power",
1.1139 +# "Print", "Printnl", "Raise",
1.1140 + "Return", "RightShift", "Slice", "Sliceobj",
1.1141 + "Stmt", "Sub", "Subscript",
1.1142 +# "TryExcept", "TryFinally",
1.1143 + "Tuple", "UnaryAdd", "UnarySub",
1.1144 + "While", "With", "Yield",
1.1145 +]
1.1146 +
1.1147 +class SafeVisitor(object):
1.1148 + """
1.1149 + Make sure code is safe by walking through the AST.
1.1150 +
1.1151 + Code considered unsafe if:
1.1152 + * it has restricted AST nodes
1.1153 + * it is trying to access resricted attributes
1.1154 +
1.1155 + Adopted from http://www.zafar.se/bkz/uploads/safe.txt (public domain, Babar K. Zafar)
1.1156 + """
1.1157 + def __init__(self):
1.1158 + "Initialize visitor by generating callbacks for all AST node types."
1.1159 + self.errors = []
1.1160 +
1.1161 + def walk(self, ast, filename):
1.1162 + "Validate each node in AST and raise SecurityError if the code is not safe."
1.1163 + self.filename = filename
1.1164 + self.visit(ast)
1.1165 +
1.1166 + if self.errors:
1.1167 + raise SecurityError, '\n'.join([str(err) for err in self.errors])
1.1168 +
1.1169 + def visit(self, node, *args):
1.1170 + "Recursively validate node and all of its children."
1.1171 + def classname(obj):
1.1172 + return obj.__class__.__name__
1.1173 + nodename = classname(node)
1.1174 + fn = getattr(self, 'visit' + nodename, None)
1.1175 +
1.1176 + if fn:
1.1177 + fn(node, *args)
1.1178 + else:
1.1179 + if nodename not in ALLOWED_AST_NODES:
1.1180 + self.fail(node, *args)
1.1181 +
1.1182 + for child in node.getChildNodes():
1.1183 + self.visit(child, *args)
1.1184 +
1.1185 + def visitName(self, node, *args):
1.1186 + "Disallow any attempts to access a restricted attr."
1.1187 + #self.assert_attr(node.getChildren()[0], node)
1.1188 + pass
1.1189 +
1.1190 + def visitGetattr(self, node, *args):
1.1191 + "Disallow any attempts to access a restricted attribute."
1.1192 + self.assert_attr(node.attrname, node)
1.1193 +
1.1194 + def assert_attr(self, attrname, node):
1.1195 + if self.is_unallowed_attr(attrname):
1.1196 + lineno = self.get_node_lineno(node)
1.1197 + e = SecurityError("%s:%d - access to attribute '%s' is denied" % (self.filename, lineno, attrname))
1.1198 + self.errors.append(e)
1.1199 +
1.1200 + def is_unallowed_attr(self, name):
1.1201 + return name.startswith('_') \
1.1202 + or name.startswith('func_') \
1.1203 + or name.startswith('im_')
1.1204 +
1.1205 + def get_node_lineno(self, node):
1.1206 + return (node.lineno) and node.lineno or 0
1.1207 +
1.1208 + def fail(self, node, *args):
1.1209 + "Default callback for unallowed AST nodes."
1.1210 + lineno = self.get_node_lineno(node)
1.1211 + nodename = node.__class__.__name__
1.1212 + e = SecurityError("%s:%d - execution of '%s' statements is denied" % (self.filename, lineno, nodename))
1.1213 + self.errors.append(e)
1.1214 +
1.1215 +class TemplateResult(object, DictMixin):
1.1216 + """Dictionary like object for storing template output.
1.1217 +
1.1218 + The result of a template execution is usally a string, but sometimes it
1.1219 + contains attributes set using $var. This class provides a simple
1.1220 + dictionary like interface for storing the output of the template and the
1.1221 + attributes. The output is stored with a special key __body__. Convering
1.1222 + the the TemplateResult to string or unicode returns the value of __body__.
1.1223 +
1.1224 + When the template is in execution, the output is generated part by part
1.1225 + and those parts are combined at the end. Parts are added to the
1.1226 + TemplateResult by calling the `extend` method and the parts are combined
1.1227 + seemlessly when __body__ is accessed.
1.1228 +
1.1229 + >>> d = TemplateResult(__body__='hello, world', x='foo')
1.1230 + >>> d
1.1231 + <TemplateResult: {'__body__': 'hello, world', 'x': 'foo'}>
1.1232 + >>> print d
1.1233 + hello, world
1.1234 + >>> d.x
1.1235 + 'foo'
1.1236 + >>> d = TemplateResult()
1.1237 + >>> d.extend([u'hello', u'world'])
1.1238 + >>> d
1.1239 + <TemplateResult: {'__body__': u'helloworld'}>
1.1240 + """
1.1241 + def __init__(self, *a, **kw):
1.1242 + self.__dict__["_d"] = dict(*a, **kw)
1.1243 + self._d.setdefault("__body__", u'')
1.1244 +
1.1245 + self.__dict__['_parts'] = []
1.1246 + self.__dict__["extend"] = self._parts.extend
1.1247 +
1.1248 + self._d.setdefault("__body__", None)
1.1249 +
1.1250 + def keys(self):
1.1251 + return self._d.keys()
1.1252 +
1.1253 + def _prepare_body(self):
1.1254 + """Prepare value of __body__ by joining parts.
1.1255 + """
1.1256 + if self._parts:
1.1257 + value = u"".join(self._parts)
1.1258 + self._parts[:] = []
1.1259 + body = self._d.get('__body__')
1.1260 + if body:
1.1261 + self._d['__body__'] = body + value
1.1262 + else:
1.1263 + self._d['__body__'] = value
1.1264 +
1.1265 + def __getitem__(self, name):
1.1266 + if name == "__body__":
1.1267 + self._prepare_body()
1.1268 + return self._d[name]
1.1269 +
1.1270 + def __setitem__(self, name, value):
1.1271 + if name == "__body__":
1.1272 + self._prepare_body()
1.1273 + return self._d.__setitem__(name, value)
1.1274 +
1.1275 + def __delitem__(self, name):
1.1276 + if name == "__body__":
1.1277 + self._prepare_body()
1.1278 + return self._d.__delitem__(name)
1.1279 +
1.1280 + def __getattr__(self, key):
1.1281 + try:
1.1282 + return self[key]
1.1283 + except KeyError, k:
1.1284 + raise AttributeError, k
1.1285 +
1.1286 + def __setattr__(self, key, value):
1.1287 + self[key] = value
1.1288 +
1.1289 + def __delattr__(self, key):
1.1290 + try:
1.1291 + del self[key]
1.1292 + except KeyError, k:
1.1293 + raise AttributeError, k
1.1294 +
1.1295 + def __unicode__(self):
1.1296 + self._prepare_body()
1.1297 + return self["__body__"]
1.1298 +
1.1299 + def __str__(self):
1.1300 + self._prepare_body()
1.1301 + return self["__body__"].encode('utf-8')
1.1302 +
1.1303 + def __repr__(self):
1.1304 + self._prepare_body()
1.1305 + return "<TemplateResult: %s>" % self._d
1.1306 +
1.1307 +def test():
1.1308 + r"""Doctest for testing template module.
1.1309 +
1.1310 + Define a utility function to run template test.
1.1311 +
1.1312 + >>> class TestResult:
1.1313 + ... def __init__(self, t): self.t = t
1.1314 + ... def __getattr__(self, name): return getattr(self.t, name)
1.1315 + ... def __repr__(self): return repr(unicode(self))
1.1316 + ...
1.1317 + >>> def t(code, **keywords):
1.1318 + ... tmpl = Template(code, **keywords)
1.1319 + ... return lambda *a, **kw: TestResult(tmpl(*a, **kw))
1.1320 + ...
1.1321 +
1.1322 + Simple tests.
1.1323 +
1.1324 + >>> t('1')()
1.1325 + u'1\n'
1.1326 + >>> t('$def with ()\n1')()
1.1327 + u'1\n'
1.1328 + >>> t('$def with (a)\n$a')(1)
1.1329 + u'1\n'
1.1330 + >>> t('$def with (a=0)\n$a')(1)
1.1331 + u'1\n'
1.1332 + >>> t('$def with (a=0)\n$a')(a=1)
1.1333 + u'1\n'
1.1334 +
1.1335 + Test complicated expressions.
1.1336 +
1.1337 + >>> t('$def with (x)\n$x.upper()')('hello')
1.1338 + u'HELLO\n'
1.1339 + >>> t('$(2 * 3 + 4 * 5)')()
1.1340 + u'26\n'
1.1341 + >>> t('${2 * 3 + 4 * 5}')()
1.1342 + u'26\n'
1.1343 + >>> t('$def with (limit)\nkeep $(limit)ing.')('go')
1.1344 + u'keep going.\n'
1.1345 + >>> t('$def with (a)\n$a.b[0]')(storage(b=[1]))
1.1346 + u'1\n'
1.1347 +
1.1348 + Test html escaping.
1.1349 +
1.1350 + >>> t('$def with (x)\n$x', filename='a.html')('<html>')
1.1351 + u'<html>\n'
1.1352 + >>> t('$def with (x)\n$x', filename='a.txt')('<html>')
1.1353 + u'<html>\n'
1.1354 +
1.1355 + Test if, for and while.
1.1356 +
1.1357 + >>> t('$if 1: 1')()
1.1358 + u'1\n'
1.1359 + >>> t('$if 1:\n 1')()
1.1360 + u'1\n'
1.1361 + >>> t('$if 1:\n 1\\')()
1.1362 + u'1'
1.1363 + >>> t('$if 0: 0\n$elif 1: 1')()
1.1364 + u'1\n'
1.1365 + >>> t('$if 0: 0\n$elif None: 0\n$else: 1')()
1.1366 + u'1\n'
1.1367 + >>> t('$if 0 < 1 and 1 < 2: 1')()
1.1368 + u'1\n'
1.1369 + >>> t('$for x in [1, 2, 3]: $x')()
1.1370 + u'1\n2\n3\n'
1.1371 + >>> t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1})
1.1372 + u'1\n'
1.1373 + >>> t('$for x in [1, 2, 3]:\n\t$x')()
1.1374 + u' 1\n 2\n 3\n'
1.1375 + >>> t('$def with (a)\n$while a and a.pop():1')([1, 2, 3])
1.1376 + u'1\n1\n1\n'
1.1377 +
1.1378 + The space after : must be ignored.
1.1379 +
1.1380 + >>> t('$if True: foo')()
1.1381 + u'foo\n'
1.1382 +
1.1383 + Test loop.xxx.
1.1384 +
1.1385 + >>> t("$for i in range(5):$loop.index, $loop.parity")()
1.1386 + u'1, odd\n2, even\n3, odd\n4, even\n5, odd\n'
1.1387 + >>> t("$for i in range(2):\n $for j in range(2):$loop.parent.parity $loop.parity")()
1.1388 + u'odd odd\nodd even\neven odd\neven even\n'
1.1389 +
1.1390 + Test assignment.
1.1391 +
1.1392 + >>> t('$ a = 1\n$a')()
1.1393 + u'1\n'
1.1394 + >>> t('$ a = [1]\n$a[0]')()
1.1395 + u'1\n'
1.1396 + >>> t('$ a = {1: 1}\n$a.keys()[0]')()
1.1397 + u'1\n'
1.1398 + >>> t('$ a = []\n$if not a: 1')()
1.1399 + u'1\n'
1.1400 + >>> t('$ a = {}\n$if not a: 1')()
1.1401 + u'1\n'
1.1402 + >>> t('$ a = -1\n$a')()
1.1403 + u'-1\n'
1.1404 + >>> t('$ a = "1"\n$a')()
1.1405 + u'1\n'
1.1406 +
1.1407 + Test comments.
1.1408 +
1.1409 + >>> t('$# 0')()
1.1410 + u'\n'
1.1411 + >>> t('hello$#comment1\nhello$#comment2')()
1.1412 + u'hello\nhello\n'
1.1413 + >>> t('$#comment0\nhello$#comment1\nhello$#comment2')()
1.1414 + u'\nhello\nhello\n'
1.1415 +
1.1416 + Test unicode.
1.1417 +
1.1418 + >>> t('$def with (a)\n$a')(u'\u203d')
1.1419 + u'\u203d\n'
1.1420 + >>> t('$def with (a)\n$a')(u'\u203d'.encode('utf-8'))
1.1421 + u'\u203d\n'
1.1422 + >>> t(u'$def with (a)\n$a $:a')(u'\u203d')
1.1423 + u'\u203d \u203d\n'
1.1424 + >>> t(u'$def with ()\nfoo')()
1.1425 + u'foo\n'
1.1426 + >>> def f(x): return x
1.1427 + ...
1.1428 + >>> t(u'$def with (f)\n$:f("x")')(f)
1.1429 + u'x\n'
1.1430 + >>> t('$def with (f)\n$:f("x")')(f)
1.1431 + u'x\n'
1.1432 +
1.1433 + Test dollar escaping.
1.1434 +
1.1435 + >>> t("Stop, $$money isn't evaluated.")()
1.1436 + u"Stop, $money isn't evaluated.\n"
1.1437 + >>> t("Stop, \$money isn't evaluated.")()
1.1438 + u"Stop, $money isn't evaluated.\n"
1.1439 +
1.1440 + Test space sensitivity.
1.1441 +
1.1442 + >>> t('$def with (x)\n$x')(1)
1.1443 + u'1\n'
1.1444 + >>> t('$def with(x ,y)\n$x')(1, 1)
1.1445 + u'1\n'
1.1446 + >>> t('$(1 + 2*3 + 4)')()
1.1447 + u'11\n'
1.1448 +
1.1449 + Make sure globals are working.
1.1450 +
1.1451 + >>> t('$x')()
1.1452 + Traceback (most recent call last):
1.1453 + ...
1.1454 + NameError: global name 'x' is not defined
1.1455 + >>> t('$x', globals={'x': 1})()
1.1456 + u'1\n'
1.1457 +
1.1458 + Can't change globals.
1.1459 +
1.1460 + >>> t('$ x = 2\n$x', globals={'x': 1})()
1.1461 + u'2\n'
1.1462 + >>> t('$ x = x + 1\n$x', globals={'x': 1})()
1.1463 + Traceback (most recent call last):
1.1464 + ...
1.1465 + UnboundLocalError: local variable 'x' referenced before assignment
1.1466 +
1.1467 + Make sure builtins are customizable.
1.1468 +
1.1469 + >>> t('$min(1, 2)')()
1.1470 + u'1\n'
1.1471 + >>> t('$min(1, 2)', builtins={})()
1.1472 + Traceback (most recent call last):
1.1473 + ...
1.1474 + NameError: global name 'min' is not defined
1.1475 +
1.1476 + Test vars.
1.1477 +
1.1478 + >>> x = t('$var x: 1')()
1.1479 + >>> x.x
1.1480 + u'1'
1.1481 + >>> x = t('$var x = 1')()
1.1482 + >>> x.x
1.1483 + 1
1.1484 + >>> x = t('$var x: \n foo\n bar')()
1.1485 + >>> x.x
1.1486 + u'foo\nbar\n'
1.1487 +
1.1488 + Test BOM chars.
1.1489 +
1.1490 + >>> t('\xef\xbb\xbf$def with(x)\n$x')('foo')
1.1491 + u'foo\n'
1.1492 +
1.1493 + Test for with weird cases.
1.1494 +
1.1495 + >>> t('$for i in range(10)[1:5]:\n $i')()
1.1496 + u'1\n2\n3\n4\n'
1.1497 + >>> t("$for k, v in {'a': 1, 'b': 2}.items():\n $k $v")()
1.1498 + u'a 1\nb 2\n'
1.1499 + >>> t("$for k, v in ({'a': 1, 'b': 2}.items():\n $k $v")()
1.1500 + Traceback (most recent call last):
1.1501 + ...
1.1502 + SyntaxError: invalid syntax
1.1503 +
1.1504 + Test datetime.
1.1505 +
1.1506 + >>> import datetime
1.1507 + >>> t("$def with (date)\n$date.strftime('%m %Y')")(datetime.datetime(2009, 1, 1))
1.1508 + u'01 2009\n'
1.1509 + """
1.1510 + pass
1.1511 +
1.1512 +if __name__ == "__main__":
1.1513 + import sys
1.1514 + if '--compile' in sys.argv:
1.1515 + compile_templates(sys.argv[2])
1.1516 + else:
1.1517 + import doctest
1.1518 + doctest.testmod()