OpenSecurity/install/web.py-0.37/web/template.py
author om
Mon, 02 Dec 2013 14:02:05 +0100
changeset 3 65432e6c6042
permissions -rwxr-xr-x
initial deployment and project layout commit
     1 """
     2 simple, elegant templating
     3 (part of web.py)
     4 
     5 Template design:
     6 
     7 Template string is split into tokens and the tokens are combined into nodes. 
     8 Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and 
     9 for-loop, if-loop etc are block nodes, which contain multiple child nodes. 
    10 
    11 Each node can emit some python string. python string emitted by the 
    12 root node is validated for safeeval and executed using python in the given environment.
    13 
    14 Enough care is taken to make sure the generated code and the template has line to line match, 
    15 so that the error messages can point to exact line number in template. (It doesn't work in some cases still.)
    16 
    17 Grammar:
    18 
    19     template -> defwith sections 
    20     defwith -> '$def with (' arguments ')' | ''
    21     sections -> section*
    22     section -> block | assignment | line
    23 
    24     assignment -> '$ ' <assignment expression>
    25     line -> (text|expr)*
    26     text -> <any characters other than $>
    27     expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}'
    28     pyexpr -> <python expression>
    29 """
    30 
    31 __all__ = [
    32     "Template",
    33     "Render", "render", "frender",
    34     "ParseError", "SecurityError",
    35     "test"
    36 ]
    37 
    38 import tokenize
    39 import os
    40 import sys
    41 import glob
    42 import re
    43 from UserDict import DictMixin
    44 import warnings
    45 
    46 from utils import storage, safeunicode, safestr, re_compile
    47 from webapi import config
    48 from net import websafe
    49 
    50 def splitline(text):
    51     r"""
    52     Splits the given text at newline.
    53     
    54         >>> splitline('foo\nbar')
    55         ('foo\n', 'bar')
    56         >>> splitline('foo')
    57         ('foo', '')
    58         >>> splitline('')
    59         ('', '')
    60     """
    61     index = text.find('\n') + 1
    62     if index:
    63         return text[:index], text[index:]
    64     else:
    65         return text, ''
    66 
    67 class Parser:
    68     """Parser Base.
    69     """
    70     def __init__(self):
    71         self.statement_nodes = STATEMENT_NODES
    72         self.keywords = KEYWORDS
    73 
    74     def parse(self, text, name="<template>"):
    75         self.text = text
    76         self.name = name
    77         
    78         defwith, text = self.read_defwith(text)
    79         suite = self.read_suite(text)
    80         return DefwithNode(defwith, suite)
    81 
    82     def read_defwith(self, text):
    83         if text.startswith('$def with'):
    84             defwith, text = splitline(text)
    85             defwith = defwith[1:].strip() # strip $ and spaces
    86             return defwith, text
    87         else:
    88             return '', text
    89     
    90     def read_section(self, text):
    91         r"""Reads one section from the given text.
    92         
    93         section -> block | assignment | line
    94         
    95             >>> read_section = Parser().read_section
    96             >>> read_section('foo\nbar\n')
    97             (<line: [t'foo\n']>, 'bar\n')
    98             >>> read_section('$ a = b + 1\nfoo\n')
    99             (<assignment: 'a = b + 1'>, 'foo\n')
   100             
   101         read_section('$for in range(10):\n    hello $i\nfoo)
   102         """
   103         if text.lstrip(' ').startswith('$'):
   104             index = text.index('$')
   105             begin_indent, text2 = text[:index], text[index+1:]
   106             ahead = self.python_lookahead(text2)
   107             
   108             if ahead == 'var':
   109                 return self.read_var(text2)
   110             elif ahead in self.statement_nodes:
   111                 return self.read_block_section(text2, begin_indent)
   112             elif ahead in self.keywords:
   113                 return self.read_keyword(text2)
   114             elif ahead.strip() == '':
   115                 # assignments starts with a space after $
   116                 # ex: $ a = b + 2
   117                 return self.read_assignment(text2)
   118         return self.readline(text)
   119         
   120     def read_var(self, text):
   121         r"""Reads a var statement.
   122         
   123             >>> read_var = Parser().read_var
   124             >>> read_var('var x=10\nfoo')
   125             (<var: x = 10>, 'foo')
   126             >>> read_var('var x: hello $name\nfoo')
   127             (<var: x = join_(u'hello ', escape_(name, True))>, 'foo')
   128         """
   129         line, text = splitline(text)
   130         tokens = self.python_tokens(line)
   131         if len(tokens) < 4:
   132             raise SyntaxError('Invalid var statement')
   133             
   134         name = tokens[1]
   135         sep = tokens[2]
   136         value = line.split(sep, 1)[1].strip()
   137         
   138         if sep == '=':
   139             pass # no need to process value
   140         elif sep == ':': 
   141             #@@ Hack for backward-compatability
   142             if tokens[3] == '\n': # multi-line var statement
   143                 block, text = self.read_indented_block(text, '    ')
   144                 lines = [self.readline(x)[0] for x in block.splitlines()]
   145                 nodes = []
   146                 for x in lines:
   147                     nodes.extend(x.nodes)
   148                     nodes.append(TextNode('\n'))         
   149             else: # single-line var statement
   150                 linenode, _ = self.readline(value)
   151                 nodes = linenode.nodes                
   152             parts = [node.emit('') for node in nodes]
   153             value = "join_(%s)" % ", ".join(parts)
   154         else:
   155             raise SyntaxError('Invalid var statement')
   156         return VarNode(name, value), text
   157                     
   158     def read_suite(self, text):
   159         r"""Reads section by section till end of text.
   160         
   161             >>> read_suite = Parser().read_suite
   162             >>> read_suite('hello $name\nfoo\n')
   163             [<line: [t'hello ', $name, t'\n']>, <line: [t'foo\n']>]
   164         """
   165         sections = []
   166         while text:
   167             section, text = self.read_section(text)
   168             sections.append(section)
   169         return SuiteNode(sections)
   170     
   171     def readline(self, text):
   172         r"""Reads one line from the text. Newline is supressed if the line ends with \.
   173         
   174             >>> readline = Parser().readline
   175             >>> readline('hello $name!\nbye!')
   176             (<line: [t'hello ', $name, t'!\n']>, 'bye!')
   177             >>> readline('hello $name!\\\nbye!')
   178             (<line: [t'hello ', $name, t'!']>, 'bye!')
   179             >>> readline('$f()\n\n')
   180             (<line: [$f(), t'\n']>, '\n')
   181         """
   182         line, text = splitline(text)
   183 
   184         # supress new line if line ends with \
   185         if line.endswith('\\\n'):
   186             line = line[:-2]
   187                 
   188         nodes = []
   189         while line:
   190             node, line = self.read_node(line)
   191             nodes.append(node)
   192             
   193         return LineNode(nodes), text
   194 
   195     def read_node(self, text):
   196         r"""Reads a node from the given text and returns the node and remaining text.
   197 
   198             >>> read_node = Parser().read_node
   199             >>> read_node('hello $name')
   200             (t'hello ', '$name')
   201             >>> read_node('$name')
   202             ($name, '')
   203         """
   204         if text.startswith('$$'):
   205             return TextNode('$'), text[2:]
   206         elif text.startswith('$#'): # comment
   207             line, text = splitline(text)
   208             return TextNode('\n'), text
   209         elif text.startswith('$'):
   210             text = text[1:] # strip $
   211             if text.startswith(':'):
   212                 escape = False
   213                 text = text[1:] # strip :
   214             else:
   215                 escape = True
   216             return self.read_expr(text, escape=escape)
   217         else:
   218             return self.read_text(text)
   219     
   220     def read_text(self, text):
   221         r"""Reads a text node from the given text.
   222         
   223             >>> read_text = Parser().read_text
   224             >>> read_text('hello $name')
   225             (t'hello ', '$name')
   226         """
   227         index = text.find('$')
   228         if index < 0:
   229             return TextNode(text), ''
   230         else:
   231             return TextNode(text[:index]), text[index:]
   232             
   233     def read_keyword(self, text):
   234         line, text = splitline(text)
   235         return StatementNode(line.strip() + "\n"), text
   236 
   237     def read_expr(self, text, escape=True):
   238         """Reads a python expression from the text and returns the expression and remaining text.
   239 
   240         expr -> simple_expr | paren_expr
   241         simple_expr -> id extended_expr
   242         extended_expr -> attr_access | paren_expr extended_expr | ''
   243         attr_access -> dot id extended_expr
   244         paren_expr -> [ tokens ] | ( tokens ) | { tokens }
   245      
   246             >>> read_expr = Parser().read_expr
   247             >>> read_expr("name")
   248             ($name, '')
   249             >>> read_expr("a.b and c")
   250             ($a.b, ' and c')
   251             >>> read_expr("a. b")
   252             ($a, '. b')
   253             >>> read_expr("name</h1>")
   254             ($name, '</h1>')
   255             >>> read_expr("(limit)ing")
   256             ($(limit), 'ing')
   257             >>> read_expr('a[1, 2][:3].f(1+2, "weird string[).", 3 + 4) done.')
   258             ($a[1, 2][:3].f(1+2, "weird string[).", 3 + 4), ' done.')
   259         """
   260         def simple_expr():
   261             identifier()
   262             extended_expr()
   263         
   264         def identifier():
   265             tokens.next()
   266         
   267         def extended_expr():
   268             lookahead = tokens.lookahead()
   269             if lookahead is None:
   270                 return
   271             elif lookahead.value == '.':
   272                 attr_access()
   273             elif lookahead.value in parens:
   274                 paren_expr()
   275                 extended_expr()
   276             else:
   277                 return
   278         
   279         def attr_access():
   280             from token import NAME # python token constants
   281             dot = tokens.lookahead()
   282             if tokens.lookahead2().type == NAME:
   283                 tokens.next() # consume dot
   284                 identifier()
   285                 extended_expr()
   286         
   287         def paren_expr():
   288             begin = tokens.next().value
   289             end = parens[begin]
   290             while True:
   291                 if tokens.lookahead().value in parens:
   292                     paren_expr()
   293                 else:
   294                     t = tokens.next()
   295                     if t.value == end:
   296                         break
   297             return
   298 
   299         parens = {
   300             "(": ")",
   301             "[": "]",
   302             "{": "}"
   303         }
   304         
   305         def get_tokens(text):
   306             """tokenize text using python tokenizer.
   307             Python tokenizer ignores spaces, but they might be important in some cases. 
   308             This function introduces dummy space tokens when it identifies any ignored space.
   309             Each token is a storage object containing type, value, begin and end.
   310             """
   311             readline = iter([text]).next
   312             end = None
   313             for t in tokenize.generate_tokens(readline):
   314                 t = storage(type=t[0], value=t[1], begin=t[2], end=t[3])
   315                 if end is not None and end != t.begin:
   316                     _, x1 = end
   317                     _, x2 = t.begin
   318                     yield storage(type=-1, value=text[x1:x2], begin=end, end=t.begin)
   319                 end = t.end
   320                 yield t
   321                 
   322         class BetterIter:
   323             """Iterator like object with 2 support for 2 look aheads."""
   324             def __init__(self, items):
   325                 self.iteritems = iter(items)
   326                 self.items = []
   327                 self.position = 0
   328                 self.current_item = None
   329             
   330             def lookahead(self):
   331                 if len(self.items) <= self.position:
   332                     self.items.append(self._next())
   333                 return self.items[self.position]
   334 
   335             def _next(self):
   336                 try:
   337                     return self.iteritems.next()
   338                 except StopIteration:
   339                     return None
   340                 
   341             def lookahead2(self):
   342                 if len(self.items) <= self.position+1:
   343                     self.items.append(self._next())
   344                 return self.items[self.position+1]
   345                     
   346             def next(self):
   347                 self.current_item = self.lookahead()
   348                 self.position += 1
   349                 return self.current_item
   350 
   351         tokens = BetterIter(get_tokens(text))
   352                 
   353         if tokens.lookahead().value in parens:
   354             paren_expr()
   355         else:
   356             simple_expr()
   357         row, col = tokens.current_item.end
   358         return ExpressionNode(text[:col], escape=escape), text[col:]    
   359 
   360     def read_assignment(self, text):
   361         r"""Reads assignment statement from text.
   362     
   363             >>> read_assignment = Parser().read_assignment
   364             >>> read_assignment('a = b + 1\nfoo')
   365             (<assignment: 'a = b + 1'>, 'foo')
   366         """
   367         line, text = splitline(text)
   368         return AssignmentNode(line.strip()), text
   369     
   370     def python_lookahead(self, text):
   371         """Returns the first python token from the given text.
   372         
   373             >>> python_lookahead = Parser().python_lookahead
   374             >>> python_lookahead('for i in range(10):')
   375             'for'
   376             >>> python_lookahead('else:')
   377             'else'
   378             >>> python_lookahead(' x = 1')
   379             ' '
   380         """
   381         readline = iter([text]).next
   382         tokens = tokenize.generate_tokens(readline)
   383         return tokens.next()[1]
   384         
   385     def python_tokens(self, text):
   386         readline = iter([text]).next
   387         tokens = tokenize.generate_tokens(readline)
   388         return [t[1] for t in tokens]
   389         
   390     def read_indented_block(self, text, indent):
   391         r"""Read a block of text. A block is what typically follows a for or it statement.
   392         It can be in the same line as that of the statement or an indented block.
   393 
   394             >>> read_indented_block = Parser().read_indented_block
   395             >>> read_indented_block('  a\n  b\nc', '  ')
   396             ('a\nb\n', 'c')
   397             >>> read_indented_block('  a\n    b\n  c\nd', '  ')
   398             ('a\n  b\nc\n', 'd')
   399             >>> read_indented_block('  a\n\n    b\nc', '  ')
   400             ('a\n\n  b\n', 'c')
   401         """
   402         if indent == '':
   403             return '', text
   404             
   405         block = ""
   406         while text:
   407             line, text2 = splitline(text)
   408             if line.strip() == "":
   409                 block += '\n'
   410             elif line.startswith(indent):
   411                 block += line[len(indent):]
   412             else:
   413                 break
   414             text = text2
   415         return block, text
   416 
   417     def read_statement(self, text):
   418         r"""Reads a python statement.
   419         
   420             >>> read_statement = Parser().read_statement
   421             >>> read_statement('for i in range(10): hello $name')
   422             ('for i in range(10):', ' hello $name')
   423         """
   424         tok = PythonTokenizer(text)
   425         tok.consume_till(':')
   426         return text[:tok.index], text[tok.index:]
   427         
   428     def read_block_section(self, text, begin_indent=''):
   429         r"""
   430             >>> read_block_section = Parser().read_block_section
   431             >>> read_block_section('for i in range(10): hello $i\nfoo')
   432             (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
   433             >>> read_block_section('for i in range(10):\n        hello $i\n    foo', begin_indent='    ')
   434             (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, '    foo')
   435             >>> read_block_section('for i in range(10):\n  hello $i\nfoo')
   436             (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
   437         """
   438         line, text = splitline(text)
   439         stmt, line = self.read_statement(line)
   440         keyword = self.python_lookahead(stmt)
   441         
   442         # if there is some thing left in the line
   443         if line.strip():
   444             block = line.lstrip()
   445         else:
   446             def find_indent(text):
   447                 rx = re_compile('  +')
   448                 match = rx.match(text)    
   449                 first_indent = match and match.group(0)
   450                 return first_indent or ""
   451 
   452             # find the indentation of the block by looking at the first line
   453             first_indent = find_indent(text)[len(begin_indent):]
   454 
   455             #TODO: fix this special case
   456             if keyword == "code":
   457                 indent = begin_indent + first_indent
   458             else:
   459                 indent = begin_indent + min(first_indent, INDENT)
   460             
   461             block, text = self.read_indented_block(text, indent)
   462             
   463         return self.create_block_node(keyword, stmt, block, begin_indent), text
   464         
   465     def create_block_node(self, keyword, stmt, block, begin_indent):
   466         if keyword in self.statement_nodes:
   467             return self.statement_nodes[keyword](stmt, block, begin_indent)
   468         else:
   469             raise ParseError, 'Unknown statement: %s' % repr(keyword)
   470         
   471 class PythonTokenizer:
   472     """Utility wrapper over python tokenizer."""
   473     def __init__(self, text):
   474         self.text = text
   475         readline = iter([text]).next
   476         self.tokens = tokenize.generate_tokens(readline)
   477         self.index = 0
   478         
   479     def consume_till(self, delim):        
   480         """Consumes tokens till colon.
   481         
   482             >>> tok = PythonTokenizer('for i in range(10): hello $i')
   483             >>> tok.consume_till(':')
   484             >>> tok.text[:tok.index]
   485             'for i in range(10):'
   486             >>> tok.text[tok.index:]
   487             ' hello $i'
   488         """
   489         try:
   490             while True:
   491                 t = self.next()
   492                 if t.value == delim:
   493                     break
   494                 elif t.value == '(':
   495                     self.consume_till(')')
   496                 elif t.value == '[':
   497                     self.consume_till(']')
   498                 elif t.value == '{':
   499                     self.consume_till('}')
   500 
   501                 # if end of line is found, it is an exception.
   502                 # Since there is no easy way to report the line number,
   503                 # leave the error reporting to the python parser later  
   504                 #@@ This should be fixed.
   505                 if t.value == '\n':
   506                     break
   507         except:
   508             #raise ParseError, "Expected %s, found end of line." % repr(delim)
   509 
   510             # raising ParseError doesn't show the line number. 
   511             # if this error is ignored, then it will be caught when compiling the python code.
   512             return
   513     
   514     def next(self):
   515         type, t, begin, end, line = self.tokens.next()
   516         row, col = end
   517         self.index = col
   518         return storage(type=type, value=t, begin=begin, end=end)
   519         
   520 class DefwithNode:
   521     def __init__(self, defwith, suite):
   522         if defwith:
   523             self.defwith = defwith.replace('with', '__template__') + ':'
   524             # offset 4 lines. for encoding, __lineoffset__, loop and self.
   525             self.defwith += "\n    __lineoffset__ = -4"
   526         else:
   527             self.defwith = 'def __template__():'
   528             # offset 4 lines for encoding, __template__, __lineoffset__, loop and self.
   529             self.defwith += "\n    __lineoffset__ = -5"
   530 
   531         self.defwith += "\n    loop = ForLoop()"
   532         self.defwith += "\n    self = TemplateResult(); extend_ = self.extend"
   533         self.suite = suite
   534         self.end = "\n    return self"
   535 
   536     def emit(self, indent):
   537         encoding = "# coding: utf-8\n"
   538         return encoding + self.defwith + self.suite.emit(indent + INDENT) + self.end
   539 
   540     def __repr__(self):
   541         return "<defwith: %s, %s>" % (self.defwith, self.suite)
   542 
   543 class TextNode:
   544     def __init__(self, value):
   545         self.value = value
   546 
   547     def emit(self, indent, begin_indent=''):
   548         return repr(safeunicode(self.value))
   549         
   550     def __repr__(self):
   551         return 't' + repr(self.value)
   552 
   553 class ExpressionNode:
   554     def __init__(self, value, escape=True):
   555         self.value = value.strip()
   556         
   557         # convert ${...} to $(...)
   558         if value.startswith('{') and value.endswith('}'):
   559             self.value = '(' + self.value[1:-1] + ')'
   560             
   561         self.escape = escape
   562 
   563     def emit(self, indent, begin_indent=''):
   564         return 'escape_(%s, %s)' % (self.value, bool(self.escape))
   565         
   566     def __repr__(self):
   567         if self.escape:
   568             escape = ''
   569         else:
   570             escape = ':'
   571         return "$%s%s" % (escape, self.value)
   572         
   573 class AssignmentNode:
   574     def __init__(self, code):
   575         self.code = code
   576         
   577     def emit(self, indent, begin_indent=''):
   578         return indent + self.code + "\n"
   579         
   580     def __repr__(self):
   581         return "<assignment: %s>" % repr(self.code)
   582         
   583 class LineNode:
   584     def __init__(self, nodes):
   585         self.nodes = nodes
   586         
   587     def emit(self, indent, text_indent='', name=''):
   588         text = [node.emit('') for node in self.nodes]
   589         if text_indent:
   590             text = [repr(text_indent)] + text
   591 
   592         return indent + "extend_([%s])\n" % ", ".join(text)        
   593     
   594     def __repr__(self):
   595         return "<line: %s>" % repr(self.nodes)
   596 
   597 INDENT = '    ' # 4 spaces
   598         
   599 class BlockNode:
   600     def __init__(self, stmt, block, begin_indent=''):
   601         self.stmt = stmt
   602         self.suite = Parser().read_suite(block)
   603         self.begin_indent = begin_indent
   604 
   605     def emit(self, indent, text_indent=''):
   606         text_indent = self.begin_indent + text_indent
   607         out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
   608         return out
   609         
   610     def __repr__(self):
   611         return "<block: %s, %s>" % (repr(self.stmt), repr(self.suite))
   612 
   613 class ForNode(BlockNode):
   614     def __init__(self, stmt, block, begin_indent=''):
   615         self.original_stmt = stmt
   616         tok = PythonTokenizer(stmt)
   617         tok.consume_till('in')
   618         a = stmt[:tok.index] # for i in
   619         b = stmt[tok.index:-1] # rest of for stmt excluding :
   620         stmt = a + ' loop.setup(' + b.strip() + '):'
   621         BlockNode.__init__(self, stmt, block, begin_indent)
   622         
   623     def __repr__(self):
   624         return "<block: %s, %s>" % (repr(self.original_stmt), repr(self.suite))
   625 
   626 class CodeNode:
   627     def __init__(self, stmt, block, begin_indent=''):
   628         # compensate one line for $code:
   629         self.code = "\n" + block
   630         
   631     def emit(self, indent, text_indent=''):
   632         import re
   633         rx = re.compile('^', re.M)
   634         return rx.sub(indent, self.code).rstrip(' ')
   635         
   636     def __repr__(self):
   637         return "<code: %s>" % repr(self.code)
   638         
   639 class StatementNode:
   640     def __init__(self, stmt):
   641         self.stmt = stmt
   642         
   643     def emit(self, indent, begin_indent=''):
   644         return indent + self.stmt
   645         
   646     def __repr__(self):
   647         return "<stmt: %s>" % repr(self.stmt)
   648         
   649 class IfNode(BlockNode):
   650     pass
   651 
   652 class ElseNode(BlockNode):
   653     pass
   654 
   655 class ElifNode(BlockNode):
   656     pass
   657 
   658 class DefNode(BlockNode):
   659     def __init__(self, *a, **kw):
   660         BlockNode.__init__(self, *a, **kw)
   661 
   662         code = CodeNode("", "")
   663         code.code = "self = TemplateResult(); extend_ = self.extend\n"
   664         self.suite.sections.insert(0, code)
   665 
   666         code = CodeNode("", "")
   667         code.code = "return self\n"
   668         self.suite.sections.append(code)
   669         
   670     def emit(self, indent, text_indent=''):
   671         text_indent = self.begin_indent + text_indent
   672         out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
   673         return indent + "__lineoffset__ -= 3\n" + out
   674 
   675 class VarNode:
   676     def __init__(self, name, value):
   677         self.name = name
   678         self.value = value
   679         
   680     def emit(self, indent, text_indent):
   681         return indent + "self[%s] = %s\n" % (repr(self.name), self.value)
   682         
   683     def __repr__(self):
   684         return "<var: %s = %s>" % (self.name, self.value)
   685 
   686 class SuiteNode:
   687     """Suite is a list of sections."""
   688     def __init__(self, sections):
   689         self.sections = sections
   690         
   691     def emit(self, indent, text_indent=''):
   692         return "\n" + "".join([s.emit(indent, text_indent) for s in self.sections])
   693         
   694     def __repr__(self):
   695         return repr(self.sections)
   696 
   697 STATEMENT_NODES = {
   698     'for': ForNode,
   699     'while': BlockNode,
   700     'if': IfNode,
   701     'elif': ElifNode,
   702     'else': ElseNode,
   703     'def': DefNode,
   704     'code': CodeNode
   705 }
   706 
   707 KEYWORDS = [
   708     "pass",
   709     "break",
   710     "continue",
   711     "return"
   712 ]
   713 
   714 TEMPLATE_BUILTIN_NAMES = [
   715     "dict", "enumerate", "float", "int", "bool", "list", "long", "reversed", 
   716     "set", "slice", "tuple", "xrange",
   717     "abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex", 
   718     "id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range",
   719     "True", "False",
   720     "None",
   721     "__import__", # some c-libraries like datetime requires __import__ to present in the namespace
   722 ]
   723 
   724 import __builtin__
   725 TEMPLATE_BUILTINS = dict([(name, getattr(__builtin__, name)) for name in TEMPLATE_BUILTIN_NAMES if name in __builtin__.__dict__])
   726 
   727 class ForLoop:
   728     """
   729     Wrapper for expression in for stament to support loop.xxx helpers.
   730     
   731         >>> loop = ForLoop()
   732         >>> for x in loop.setup(['a', 'b', 'c']):
   733         ...     print loop.index, loop.revindex, loop.parity, x
   734         ...
   735         1 3 odd a
   736         2 2 even b
   737         3 1 odd c
   738         >>> loop.index
   739         Traceback (most recent call last):
   740             ...
   741         AttributeError: index
   742     """
   743     def __init__(self):
   744         self._ctx = None
   745         
   746     def __getattr__(self, name):
   747         if self._ctx is None:
   748             raise AttributeError, name
   749         else:
   750             return getattr(self._ctx, name)
   751         
   752     def setup(self, seq):        
   753         self._push()
   754         return self._ctx.setup(seq)
   755         
   756     def _push(self):
   757         self._ctx = ForLoopContext(self, self._ctx)
   758         
   759     def _pop(self):
   760         self._ctx = self._ctx.parent
   761                 
   762 class ForLoopContext:
   763     """Stackable context for ForLoop to support nested for loops.
   764     """
   765     def __init__(self, forloop, parent):
   766         self._forloop = forloop
   767         self.parent = parent
   768         
   769     def setup(self, seq):
   770         try:
   771             self.length = len(seq)
   772         except:
   773             self.length = 0
   774 
   775         self.index = 0
   776         for a in seq:
   777             self.index += 1
   778             yield a
   779         self._forloop._pop()
   780             
   781     index0 = property(lambda self: self.index-1)
   782     first = property(lambda self: self.index == 1)
   783     last = property(lambda self: self.index == self.length)
   784     odd = property(lambda self: self.index % 2 == 1)
   785     even = property(lambda self: self.index % 2 == 0)
   786     parity = property(lambda self: ['odd', 'even'][self.even])
   787     revindex0 = property(lambda self: self.length - self.index)
   788     revindex = property(lambda self: self.length - self.index + 1)
   789         
   790 class BaseTemplate:
   791     def __init__(self, code, filename, filter, globals, builtins):
   792         self.filename = filename
   793         self.filter = filter
   794         self._globals = globals
   795         self._builtins = builtins
   796         if code:
   797             self.t = self._compile(code)
   798         else:
   799             self.t = lambda: ''
   800         
   801     def _compile(self, code):
   802         env = self.make_env(self._globals or {}, self._builtins)
   803         exec(code, env)
   804         return env['__template__']
   805 
   806     def __call__(self, *a, **kw):
   807         __hidetraceback__ = True
   808         return self.t(*a, **kw)
   809 
   810     def make_env(self, globals, builtins):
   811         return dict(globals,
   812             __builtins__=builtins, 
   813             ForLoop=ForLoop,
   814             TemplateResult=TemplateResult,
   815             escape_=self._escape,
   816             join_=self._join
   817         )
   818     def _join(self, *items):
   819         return u"".join(items)
   820             
   821     def _escape(self, value, escape=False):
   822         if value is None: 
   823             value = ''
   824             
   825         value = safeunicode(value)
   826         if escape and self.filter:
   827             value = self.filter(value)
   828         return value
   829 
   830 class Template(BaseTemplate):
   831     CONTENT_TYPES = {
   832         '.html' : 'text/html; charset=utf-8',
   833         '.xhtml' : 'application/xhtml+xml; charset=utf-8',
   834         '.txt' : 'text/plain',
   835     }
   836     FILTERS = {
   837         '.html': websafe,
   838         '.xhtml': websafe,
   839         '.xml': websafe
   840     }
   841     globals = {}
   842     
   843     def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None, extensions=None):
   844         self.extensions = extensions or []
   845         text = Template.normalize_text(text)
   846         code = self.compile_template(text, filename)
   847                 
   848         _, ext = os.path.splitext(filename)
   849         filter = filter or self.FILTERS.get(ext, None)
   850         self.content_type = self.CONTENT_TYPES.get(ext, None)
   851 
   852         if globals is None:
   853             globals = self.globals
   854         if builtins is None:
   855             builtins = TEMPLATE_BUILTINS
   856                 
   857         BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
   858         
   859     def normalize_text(text):
   860         """Normalizes template text by correcting \r\n, tabs and BOM chars."""
   861         text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
   862         if not text.endswith('\n'):
   863             text += '\n'
   864 
   865         # ignore BOM chars at the begining of template
   866         BOM = '\xef\xbb\xbf'
   867         if isinstance(text, str) and text.startswith(BOM):
   868             text = text[len(BOM):]
   869         
   870         # support fort \$ for backward-compatibility 
   871         text = text.replace(r'\$', '$$')
   872         return text
   873     normalize_text = staticmethod(normalize_text)
   874                 
   875     def __call__(self, *a, **kw):
   876         __hidetraceback__ = True
   877         import webapi as web
   878         if 'headers' in web.ctx and self.content_type:
   879             web.header('Content-Type', self.content_type, unique=True)
   880             
   881         return BaseTemplate.__call__(self, *a, **kw)
   882         
   883     def generate_code(text, filename, parser=None):
   884         # parse the text
   885         parser = parser or Parser()
   886         rootnode = parser.parse(text, filename)
   887                 
   888         # generate python code from the parse tree
   889         code = rootnode.emit(indent="").strip()
   890         return safestr(code)
   891         
   892     generate_code = staticmethod(generate_code)
   893     
   894     def create_parser(self):
   895         p = Parser()
   896         for ext in self.extensions:
   897             p = ext(p)
   898         return p
   899                 
   900     def compile_template(self, template_string, filename):
   901         code = Template.generate_code(template_string, filename, parser=self.create_parser())
   902 
   903         def get_source_line(filename, lineno):
   904             try:
   905                 lines = open(filename).read().splitlines()
   906                 return lines[lineno]
   907             except:
   908                 return None
   909         
   910         try:
   911             # compile the code first to report the errors, if any, with the filename
   912             compiled_code = compile(code, filename, 'exec')
   913         except SyntaxError, e:
   914             # display template line that caused the error along with the traceback.
   915             try:
   916                 e.msg += '\n\nTemplate traceback:\n    File %s, line %s\n        %s' % \
   917                     (repr(e.filename), e.lineno, get_source_line(e.filename, e.lineno-1))
   918             except: 
   919                 pass
   920             raise
   921         
   922         # make sure code is safe - but not with jython, it doesn't have a working compiler module
   923         if not sys.platform.startswith('java'):
   924             try:
   925                 import compiler
   926                 ast = compiler.parse(code)
   927                 SafeVisitor().walk(ast, filename)
   928             except ImportError:
   929                 warnings.warn("Unabled to import compiler module. Unable to check templates for safety.")
   930         else:
   931             warnings.warn("SECURITY ISSUE: You are using Jython, which does not support checking templates for safety. Your templates can execute arbitrary code.")
   932 
   933         return compiled_code
   934         
   935 class CompiledTemplate(Template):
   936     def __init__(self, f, filename):
   937         Template.__init__(self, '', filename)
   938         self.t = f
   939         
   940     def compile_template(self, *a):
   941         return None
   942     
   943     def _compile(self, *a):
   944         return None
   945                 
   946 class Render:
   947     """The most preferred way of using templates.
   948     
   949         render = web.template.render('templates')
   950         print render.foo()
   951         
   952     Optional parameter can be `base` can be used to pass output of 
   953     every template through the base template.
   954     
   955         render = web.template.render('templates', base='layout')
   956     """
   957     def __init__(self, loc='templates', cache=None, base=None, **keywords):
   958         self._loc = loc
   959         self._keywords = keywords
   960 
   961         if cache is None:
   962             cache = not config.get('debug', False)
   963         
   964         if cache:
   965             self._cache = {}
   966         else:
   967             self._cache = None
   968         
   969         if base and not hasattr(base, '__call__'):
   970             # make base a function, so that it can be passed to sub-renders
   971             self._base = lambda page: self._template(base)(page)
   972         else:
   973             self._base = base
   974     
   975     def _add_global(self, obj, name=None):
   976         """Add a global to this rendering instance."""
   977         if 'globals' not in self._keywords: self._keywords['globals'] = {}
   978         if not name:
   979             name = obj.__name__
   980         self._keywords['globals'][name] = obj
   981     
   982     def _lookup(self, name):
   983         path = os.path.join(self._loc, name)
   984         if os.path.isdir(path):
   985             return 'dir', path
   986         else:
   987             path = self._findfile(path)
   988             if path:
   989                 return 'file', path
   990             else:
   991                 return 'none', None
   992         
   993     def _load_template(self, name):
   994         kind, path = self._lookup(name)
   995         
   996         if kind == 'dir':
   997             return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
   998         elif kind == 'file':
   999             return Template(open(path).read(), filename=path, **self._keywords)
  1000         else:
  1001             raise AttributeError, "No template named " + name            
  1002 
  1003     def _findfile(self, path_prefix): 
  1004         p = [f for f in glob.glob(path_prefix + '.*') if not f.endswith('~')] # skip backup files
  1005         p.sort() # sort the matches for deterministic order
  1006         return p and p[0]
  1007             
  1008     def _template(self, name):
  1009         if self._cache is not None:
  1010             if name not in self._cache:
  1011                 self._cache[name] = self._load_template(name)
  1012             return self._cache[name]
  1013         else:
  1014             return self._load_template(name)
  1015         
  1016     def __getattr__(self, name):
  1017         t = self._template(name)
  1018         if self._base and isinstance(t, Template):
  1019             def template(*a, **kw):
  1020                 return self._base(t(*a, **kw))
  1021             return template
  1022         else:
  1023             return self._template(name)
  1024 
  1025 class GAE_Render(Render):
  1026     # Render gets over-written. make a copy here.
  1027     super = Render
  1028     def __init__(self, loc, *a, **kw):
  1029         GAE_Render.super.__init__(self, loc, *a, **kw)
  1030         
  1031         import types
  1032         if isinstance(loc, types.ModuleType):
  1033             self.mod = loc
  1034         else:
  1035             name = loc.rstrip('/').replace('/', '.')
  1036             self.mod = __import__(name, None, None, ['x'])
  1037 
  1038         self.mod.__dict__.update(kw.get('builtins', TEMPLATE_BUILTINS))
  1039         self.mod.__dict__.update(Template.globals)
  1040         self.mod.__dict__.update(kw.get('globals', {}))
  1041 
  1042     def _load_template(self, name):
  1043         t = getattr(self.mod, name)
  1044         import types
  1045         if isinstance(t, types.ModuleType):
  1046             return GAE_Render(t, cache=self._cache is not None, base=self._base, **self._keywords)
  1047         else:
  1048             return t
  1049 
  1050 render = Render
  1051 # setup render for Google App Engine.
  1052 try:
  1053     from google import appengine
  1054     render = Render = GAE_Render
  1055 except ImportError:
  1056     pass
  1057         
  1058 def frender(path, **keywords):
  1059     """Creates a template from the given file path.
  1060     """
  1061     return Template(open(path).read(), filename=path, **keywords)
  1062     
  1063 def compile_templates(root):
  1064     """Compiles templates to python code."""
  1065     re_start = re_compile('^', re.M)
  1066     
  1067     for dirpath, dirnames, filenames in os.walk(root):
  1068         filenames = [f for f in filenames if not f.startswith('.') and not f.endswith('~') and not f.startswith('__init__.py')]
  1069 
  1070         for d in dirnames[:]:
  1071             if d.startswith('.'):
  1072                 dirnames.remove(d) # don't visit this dir
  1073 
  1074         out = open(os.path.join(dirpath, '__init__.py'), 'w')
  1075         out.write('from web.template import CompiledTemplate, ForLoop, TemplateResult\n\n')
  1076         if dirnames:
  1077             out.write("import " + ", ".join(dirnames))
  1078         out.write("\n")
  1079 
  1080         for f in filenames:
  1081             path = os.path.join(dirpath, f)
  1082 
  1083             if '.' in f:
  1084                 name, _ = f.split('.', 1)
  1085             else:
  1086                 name = f
  1087                 
  1088             text = open(path).read()
  1089             text = Template.normalize_text(text)
  1090             code = Template.generate_code(text, path)
  1091 
  1092             code = code.replace("__template__", name, 1)
  1093             
  1094             out.write(code)
  1095 
  1096             out.write('\n\n')
  1097             out.write('%s = CompiledTemplate(%s, %s)\n' % (name, name, repr(path)))
  1098             out.write("join_ = %s._join; escape_ = %s._escape\n\n" % (name, name))
  1099 
  1100             # create template to make sure it compiles
  1101             t = Template(open(path).read(), path)
  1102         out.close()
  1103                 
  1104 class ParseError(Exception):
  1105     pass
  1106     
  1107 class SecurityError(Exception):
  1108     """The template seems to be trying to do something naughty."""
  1109     pass
  1110 
  1111 # Enumerate all the allowed AST nodes
  1112 ALLOWED_AST_NODES = [
  1113     "Add", "And",
  1114 #   "AssAttr",
  1115     "AssList", "AssName", "AssTuple",
  1116 #   "Assert",
  1117     "Assign", "AugAssign",
  1118 #   "Backquote",
  1119     "Bitand", "Bitor", "Bitxor", "Break",
  1120     "CallFunc","Class", "Compare", "Const", "Continue",
  1121     "Decorators", "Dict", "Discard", "Div",
  1122     "Ellipsis", "EmptyNode",
  1123 #   "Exec",
  1124     "Expression", "FloorDiv", "For",
  1125 #   "From",
  1126     "Function", 
  1127     "GenExpr", "GenExprFor", "GenExprIf", "GenExprInner",
  1128     "Getattr", 
  1129 #   "Global", 
  1130     "If", "IfExp",
  1131 #   "Import",
  1132     "Invert", "Keyword", "Lambda", "LeftShift",
  1133     "List", "ListComp", "ListCompFor", "ListCompIf", "Mod",
  1134     "Module",
  1135     "Mul", "Name", "Not", "Or", "Pass", "Power",
  1136 #   "Print", "Printnl", "Raise",
  1137     "Return", "RightShift", "Slice", "Sliceobj",
  1138     "Stmt", "Sub", "Subscript",
  1139 #   "TryExcept", "TryFinally",
  1140     "Tuple", "UnaryAdd", "UnarySub",
  1141     "While", "With", "Yield",
  1142 ]
  1143 
  1144 class SafeVisitor(object):
  1145     """
  1146     Make sure code is safe by walking through the AST.
  1147     
  1148     Code considered unsafe if:
  1149         * it has restricted AST nodes
  1150         * it is trying to access resricted attributes   
  1151         
  1152     Adopted from http://www.zafar.se/bkz/uploads/safe.txt (public domain, Babar K. Zafar)
  1153     """
  1154     def __init__(self):
  1155         "Initialize visitor by generating callbacks for all AST node types."
  1156         self.errors = []
  1157 
  1158     def walk(self, ast, filename):
  1159         "Validate each node in AST and raise SecurityError if the code is not safe."
  1160         self.filename = filename
  1161         self.visit(ast)
  1162         
  1163         if self.errors:        
  1164             raise SecurityError, '\n'.join([str(err) for err in self.errors])
  1165         
  1166     def visit(self, node, *args):
  1167         "Recursively validate node and all of its children."
  1168         def classname(obj):
  1169             return obj.__class__.__name__
  1170         nodename = classname(node)
  1171         fn = getattr(self, 'visit' + nodename, None)
  1172         
  1173         if fn:
  1174             fn(node, *args)
  1175         else:
  1176             if nodename not in ALLOWED_AST_NODES:
  1177                 self.fail(node, *args)
  1178             
  1179         for child in node.getChildNodes():
  1180             self.visit(child, *args)
  1181 
  1182     def visitName(self, node, *args):
  1183         "Disallow any attempts to access a restricted attr."
  1184         #self.assert_attr(node.getChildren()[0], node)
  1185         pass
  1186         
  1187     def visitGetattr(self, node, *args):
  1188         "Disallow any attempts to access a restricted attribute."
  1189         self.assert_attr(node.attrname, node)
  1190             
  1191     def assert_attr(self, attrname, node):
  1192         if self.is_unallowed_attr(attrname):
  1193             lineno = self.get_node_lineno(node)
  1194             e = SecurityError("%s:%d - access to attribute '%s' is denied" % (self.filename, lineno, attrname))
  1195             self.errors.append(e)
  1196 
  1197     def is_unallowed_attr(self, name):
  1198         return name.startswith('_') \
  1199             or name.startswith('func_') \
  1200             or name.startswith('im_')
  1201             
  1202     def get_node_lineno(self, node):
  1203         return (node.lineno) and node.lineno or 0
  1204         
  1205     def fail(self, node, *args):
  1206         "Default callback for unallowed AST nodes."
  1207         lineno = self.get_node_lineno(node)
  1208         nodename = node.__class__.__name__
  1209         e = SecurityError("%s:%d - execution of '%s' statements is denied" % (self.filename, lineno, nodename))
  1210         self.errors.append(e)
  1211 
  1212 class TemplateResult(object, DictMixin):
  1213     """Dictionary like object for storing template output.
  1214     
  1215     The result of a template execution is usally a string, but sometimes it
  1216     contains attributes set using $var. This class provides a simple
  1217     dictionary like interface for storing the output of the template and the
  1218     attributes. The output is stored with a special key __body__. Convering
  1219     the the TemplateResult to string or unicode returns the value of __body__.
  1220     
  1221     When the template is in execution, the output is generated part by part
  1222     and those parts are combined at the end. Parts are added to the
  1223     TemplateResult by calling the `extend` method and the parts are combined
  1224     seemlessly when __body__ is accessed.
  1225     
  1226         >>> d = TemplateResult(__body__='hello, world', x='foo')
  1227         >>> d
  1228         <TemplateResult: {'__body__': 'hello, world', 'x': 'foo'}>
  1229         >>> print d
  1230         hello, world
  1231         >>> d.x
  1232         'foo'
  1233         >>> d = TemplateResult()
  1234         >>> d.extend([u'hello', u'world'])
  1235         >>> d
  1236         <TemplateResult: {'__body__': u'helloworld'}>
  1237     """
  1238     def __init__(self, *a, **kw):
  1239         self.__dict__["_d"] = dict(*a, **kw)
  1240         self._d.setdefault("__body__", u'')
  1241         
  1242         self.__dict__['_parts'] = []
  1243         self.__dict__["extend"] = self._parts.extend
  1244         
  1245         self._d.setdefault("__body__", None)
  1246     
  1247     def keys(self):
  1248         return self._d.keys()
  1249         
  1250     def _prepare_body(self):
  1251         """Prepare value of __body__ by joining parts.
  1252         """
  1253         if self._parts:
  1254             value = u"".join(self._parts)
  1255             self._parts[:] = []
  1256             body = self._d.get('__body__')
  1257             if body:
  1258                 self._d['__body__'] = body + value
  1259             else:
  1260                 self._d['__body__'] = value
  1261                 
  1262     def __getitem__(self, name):
  1263         if name == "__body__":
  1264             self._prepare_body()
  1265         return self._d[name]
  1266         
  1267     def __setitem__(self, name, value):
  1268         if name == "__body__":
  1269             self._prepare_body()
  1270         return self._d.__setitem__(name, value)
  1271         
  1272     def __delitem__(self, name):
  1273         if name == "__body__":
  1274             self._prepare_body()
  1275         return self._d.__delitem__(name)
  1276 
  1277     def __getattr__(self, key): 
  1278         try:
  1279             return self[key]
  1280         except KeyError, k:
  1281             raise AttributeError, k
  1282 
  1283     def __setattr__(self, key, value): 
  1284         self[key] = value
  1285 
  1286     def __delattr__(self, key):
  1287         try:
  1288             del self[key]
  1289         except KeyError, k:
  1290             raise AttributeError, k
  1291         
  1292     def __unicode__(self):
  1293         self._prepare_body()
  1294         return self["__body__"]
  1295     
  1296     def __str__(self):
  1297         self._prepare_body()
  1298         return self["__body__"].encode('utf-8')
  1299         
  1300     def __repr__(self):
  1301         self._prepare_body()
  1302         return "<TemplateResult: %s>" % self._d
  1303 
  1304 def test():
  1305     r"""Doctest for testing template module.
  1306 
  1307     Define a utility function to run template test.
  1308     
  1309         >>> class TestResult:
  1310         ...     def __init__(self, t): self.t = t
  1311         ...     def __getattr__(self, name): return getattr(self.t, name)
  1312         ...     def __repr__(self): return repr(unicode(self))
  1313         ...
  1314         >>> def t(code, **keywords):
  1315         ...     tmpl = Template(code, **keywords)
  1316         ...     return lambda *a, **kw: TestResult(tmpl(*a, **kw))
  1317         ...
  1318     
  1319     Simple tests.
  1320     
  1321         >>> t('1')()
  1322         u'1\n'
  1323         >>> t('$def with ()\n1')()
  1324         u'1\n'
  1325         >>> t('$def with (a)\n$a')(1)
  1326         u'1\n'
  1327         >>> t('$def with (a=0)\n$a')(1)
  1328         u'1\n'
  1329         >>> t('$def with (a=0)\n$a')(a=1)
  1330         u'1\n'
  1331     
  1332     Test complicated expressions.
  1333         
  1334         >>> t('$def with (x)\n$x.upper()')('hello')
  1335         u'HELLO\n'
  1336         >>> t('$(2 * 3 + 4 * 5)')()
  1337         u'26\n'
  1338         >>> t('${2 * 3 + 4 * 5}')()
  1339         u'26\n'
  1340         >>> t('$def with (limit)\nkeep $(limit)ing.')('go')
  1341         u'keep going.\n'
  1342         >>> t('$def with (a)\n$a.b[0]')(storage(b=[1]))
  1343         u'1\n'
  1344         
  1345     Test html escaping.
  1346     
  1347         >>> t('$def with (x)\n$x', filename='a.html')('<html>')
  1348         u'&lt;html&gt;\n'
  1349         >>> t('$def with (x)\n$x', filename='a.txt')('<html>')
  1350         u'<html>\n'
  1351                 
  1352     Test if, for and while.
  1353     
  1354         >>> t('$if 1: 1')()
  1355         u'1\n'
  1356         >>> t('$if 1:\n    1')()
  1357         u'1\n'
  1358         >>> t('$if 1:\n    1\\')()
  1359         u'1'
  1360         >>> t('$if 0: 0\n$elif 1: 1')()
  1361         u'1\n'
  1362         >>> t('$if 0: 0\n$elif None: 0\n$else: 1')()
  1363         u'1\n'
  1364         >>> t('$if 0 < 1 and 1 < 2: 1')()
  1365         u'1\n'
  1366         >>> t('$for x in [1, 2, 3]: $x')()
  1367         u'1\n2\n3\n'
  1368         >>> t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1})
  1369         u'1\n'
  1370         >>> t('$for x in [1, 2, 3]:\n\t$x')()
  1371         u'    1\n    2\n    3\n'
  1372         >>> t('$def with (a)\n$while a and a.pop():1')([1, 2, 3])
  1373         u'1\n1\n1\n'
  1374 
  1375     The space after : must be ignored.
  1376     
  1377         >>> t('$if True: foo')()
  1378         u'foo\n'
  1379     
  1380     Test loop.xxx.
  1381 
  1382         >>> t("$for i in range(5):$loop.index, $loop.parity")()
  1383         u'1, odd\n2, even\n3, odd\n4, even\n5, odd\n'
  1384         >>> t("$for i in range(2):\n    $for j in range(2):$loop.parent.parity $loop.parity")()
  1385         u'odd odd\nodd even\neven odd\neven even\n'
  1386         
  1387     Test assignment.
  1388     
  1389         >>> t('$ a = 1\n$a')()
  1390         u'1\n'
  1391         >>> t('$ a = [1]\n$a[0]')()
  1392         u'1\n'
  1393         >>> t('$ a = {1: 1}\n$a.keys()[0]')()
  1394         u'1\n'
  1395         >>> t('$ a = []\n$if not a: 1')()
  1396         u'1\n'
  1397         >>> t('$ a = {}\n$if not a: 1')()
  1398         u'1\n'
  1399         >>> t('$ a = -1\n$a')()
  1400         u'-1\n'
  1401         >>> t('$ a = "1"\n$a')()
  1402         u'1\n'
  1403 
  1404     Test comments.
  1405     
  1406         >>> t('$# 0')()
  1407         u'\n'
  1408         >>> t('hello$#comment1\nhello$#comment2')()
  1409         u'hello\nhello\n'
  1410         >>> t('$#comment0\nhello$#comment1\nhello$#comment2')()
  1411         u'\nhello\nhello\n'
  1412         
  1413     Test unicode.
  1414     
  1415         >>> t('$def with (a)\n$a')(u'\u203d')
  1416         u'\u203d\n'
  1417         >>> t('$def with (a)\n$a')(u'\u203d'.encode('utf-8'))
  1418         u'\u203d\n'
  1419         >>> t(u'$def with (a)\n$a $:a')(u'\u203d')
  1420         u'\u203d \u203d\n'
  1421         >>> t(u'$def with ()\nfoo')()
  1422         u'foo\n'
  1423         >>> def f(x): return x
  1424         ...
  1425         >>> t(u'$def with (f)\n$:f("x")')(f)
  1426         u'x\n'
  1427         >>> t('$def with (f)\n$:f("x")')(f)
  1428         u'x\n'
  1429     
  1430     Test dollar escaping.
  1431     
  1432         >>> t("Stop, $$money isn't evaluated.")()
  1433         u"Stop, $money isn't evaluated.\n"
  1434         >>> t("Stop, \$money isn't evaluated.")()
  1435         u"Stop, $money isn't evaluated.\n"
  1436         
  1437     Test space sensitivity.
  1438     
  1439         >>> t('$def with (x)\n$x')(1)
  1440         u'1\n'
  1441         >>> t('$def with(x ,y)\n$x')(1, 1)
  1442         u'1\n'
  1443         >>> t('$(1 + 2*3 + 4)')()
  1444         u'11\n'
  1445         
  1446     Make sure globals are working.
  1447             
  1448         >>> t('$x')()
  1449         Traceback (most recent call last):
  1450             ...
  1451         NameError: global name 'x' is not defined
  1452         >>> t('$x', globals={'x': 1})()
  1453         u'1\n'
  1454         
  1455     Can't change globals.
  1456     
  1457         >>> t('$ x = 2\n$x', globals={'x': 1})()
  1458         u'2\n'
  1459         >>> t('$ x = x + 1\n$x', globals={'x': 1})()
  1460         Traceback (most recent call last):
  1461             ...
  1462         UnboundLocalError: local variable 'x' referenced before assignment
  1463     
  1464     Make sure builtins are customizable.
  1465     
  1466         >>> t('$min(1, 2)')()
  1467         u'1\n'
  1468         >>> t('$min(1, 2)', builtins={})()
  1469         Traceback (most recent call last):
  1470             ...
  1471         NameError: global name 'min' is not defined
  1472         
  1473     Test vars.
  1474     
  1475         >>> x = t('$var x: 1')()
  1476         >>> x.x
  1477         u'1'
  1478         >>> x = t('$var x = 1')()
  1479         >>> x.x
  1480         1
  1481         >>> x = t('$var x:  \n    foo\n    bar')()
  1482         >>> x.x
  1483         u'foo\nbar\n'
  1484 
  1485     Test BOM chars.
  1486 
  1487         >>> t('\xef\xbb\xbf$def with(x)\n$x')('foo')
  1488         u'foo\n'
  1489 
  1490     Test for with weird cases.
  1491 
  1492         >>> t('$for i in range(10)[1:5]:\n    $i')()
  1493         u'1\n2\n3\n4\n'
  1494         >>> t("$for k, v in {'a': 1, 'b': 2}.items():\n    $k $v")()
  1495         u'a 1\nb 2\n'
  1496         >>> t("$for k, v in ({'a': 1, 'b': 2}.items():\n    $k $v")()
  1497         Traceback (most recent call last):
  1498             ...
  1499         SyntaxError: invalid syntax
  1500 
  1501     Test datetime.
  1502 
  1503         >>> import datetime
  1504         >>> t("$def with (date)\n$date.strftime('%m %Y')")(datetime.datetime(2009, 1, 1))
  1505         u'01 2009\n'
  1506     """
  1507     pass
  1508             
  1509 if __name__ == "__main__":
  1510     import sys
  1511     if '--compile' in sys.argv:
  1512         compile_templates(sys.argv[2])
  1513     else:
  1514         import doctest
  1515         doctest.testmod()