Package web2py :: Package gluon :: Module template
[hide private]
[frames] | no frames]

Source Code for Module web2py.gluon.template

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework (Copyrighted, 2007-2011). 
  6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  7   
  8  Author: Thadeus Burgess 
  9   
 10  Contributors: 
 11   
 12  - Thank you to Massimo Di Pierro for creating the original gluon/template.py 
 13  - Thank you to Jonathan Lundell for extensively testing the regex on Jython. 
 14  - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. 
 15  """ 
 16   
 17  import os 
 18  import re 
 19  import cgi 
 20  import cStringIO 
 21  import logging 
 22  try: 
 23      from restricted import RestrictedError 
 24  except: 
25 - def RestrictedError(a,b,c):
26 logging.error(str(a)+':'+str(b)+':'+str(c)) 27 return RuntimeError
28
29 -class Node(object):
30 """ 31 Basic Container Object 32 """
33 - def __init__(self, value = None, pre_extend = False):
34 self.value = value 35 self.pre_extend = pre_extend
36
37 - def __str__(self):
38 return str(self.value)
39
40 -class SuperNode(Node):
41 - def __init__(self, name = '', pre_extend = False):
42 self.name = name 43 self.value = None 44 self.pre_extend = pre_extend
45
46 - def __str__(self):
47 if self.value: 48 return str(self.value) 49 else: 50 raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \ 51 "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
52
53 - def __repr__(self):
54 return "%s->%s" % (self.name, self.value)
55
56 -class BlockNode(Node):
57 """ 58 Block Container. 59 60 This Node can contain other Nodes and will render in a hierarchical order 61 of when nodes were added. 62 63 ie:: 64 65 {{ block test }} 66 This is default block test 67 {{ end }} 68 """
69 - def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')):
70 """ 71 name - Name of this Node. 72 """ 73 self.nodes = [] 74 self.name = name 75 self.pre_extend = pre_extend 76 self.left, self.right = delimiters
77
78 - def __repr__(self):
79 lines = ['%sblock %s%s' % (self.left,self.name,self.right)] 80 for node in self.nodes: 81 lines.append(str(node)) 82 lines.append('%send%s' % (self.left, self.right)) 83 return ''.join(lines)
84
85 - def __str__(self):
86 """ 87 Get this BlockNodes content, not including child Nodes 88 """ 89 lines = [] 90 for node in self.nodes: 91 if not isinstance(node, BlockNode): 92 lines.append(str(node)) 93 return ''.join(lines)
94
95 - def append(self, node):
96 """ 97 Add an element to the nodes. 98 99 Keyword Arguments 100 101 - node -- Node object or string to append. 102 """ 103 if isinstance(node, str) or isinstance(node, Node): 104 self.nodes.append(node) 105 else: 106 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
107
108 - def extend(self, other):
109 """ 110 Extend the list of nodes with another BlockNode class. 111 112 Keyword Arguments 113 114 - other -- BlockNode or Content object to extend from. 115 """ 116 if isinstance(other, BlockNode): 117 self.nodes.extend(other.nodes) 118 else: 119 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
120
121 - def output(self, blocks):
122 """ 123 Merges all nodes into a single string. 124 125 blocks -- Dictionary of blocks that are extending 126 from this template. 127 """ 128 lines = [] 129 # Get each of our nodes 130 for node in self.nodes: 131 # If we have a block level node. 132 if isinstance(node, BlockNode): 133 # If we can override this block. 134 if node.name in blocks: 135 # Override block from vars. 136 lines.append(blocks[node.name].output(blocks)) 137 # Else we take the default 138 else: 139 lines.append(node.output(blocks)) 140 # Else its just a string 141 else: 142 lines.append(str(node)) 143 # Now combine all of our lines together. 144 return ''.join(lines)
145
146 -class Content(BlockNode):
147 """ 148 Parent Container -- Used as the root level BlockNode. 149 150 Contains functions that operate as such. 151 """
152 - def __init__(self, name = "ContentBlock", pre_extend = False):
153 """ 154 Keyword Arguments 155 156 name -- Unique name for this BlockNode 157 """ 158 self.name = name 159 self.nodes = [] 160 self.blocks = {} 161 self.pre_extend = pre_extend
162
163 - def __str__(self):
164 lines = [] 165 # For each of our nodes 166 for node in self.nodes: 167 # If it is a block node. 168 if isinstance(node, BlockNode): 169 # And the node has a name that corresponds with a block in us 170 if node.name in self.blocks: 171 # Use the overriding output. 172 lines.append(self.blocks[node.name].output(self.blocks)) 173 else: 174 # Otherwise we just use the nodes output. 175 lines.append(node.output(self.blocks)) 176 else: 177 # It is just a string, so include it. 178 lines.append(str(node)) 179 # Merge our list together. 180 return ''.join(lines)
181
182 - def _insert(self, other, index = 0):
183 """ 184 Inserts object at index. 185 """ 186 if isinstance(other, str) or isinstance(other, Node): 187 self.nodes.insert(index, other) 188 else: 189 raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
190
191 - def insert(self, other, index = 0):
192 """ 193 Inserts object at index. 194 195 You may pass a list of objects and have them inserted. 196 """ 197 if isinstance(other, (list, tuple)): 198 # Must reverse so the order stays the same. 199 other.reverse() 200 for item in other: 201 self._insert(item, index) 202 else: 203 self._insert(other, index)
204
205 - def append(self, node):
206 """ 207 Adds a node to list. If it is a BlockNode then we assign a block for it. 208 """ 209 if isinstance(node, str) or isinstance(node, Node): 210 self.nodes.append(node) 211 if isinstance(node, BlockNode): 212 self.blocks[node.name] = node 213 else: 214 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
215
216 - def extend(self, other):
217 """ 218 Extends the objects list of nodes with another objects nodes 219 """ 220 if isinstance(other, BlockNode): 221 self.nodes.extend(other.nodes) 222 self.blocks.update(other.blocks) 223 else: 224 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
225
226 - def clear_content(self):
227 self.nodes = []
228
229 -class TemplateParser(object):
230 231 default_delimiters = ('{{','}}') 232 r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL) 233 234 r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL) 235 236 # These are used for re-indentation. 237 # Indent + 1 238 re_block = re.compile('^(elif |else:|except:|except |finally:).*$', 239 re.DOTALL) 240 # Indent - 1 241 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) 242 # Indent - 1 243 re_pass = re.compile('^pass( .*)?$', re.DOTALL) 244
245 - def __init__(self, text, 246 name = "ParserContainer", 247 context = dict(), 248 path = 'views/', 249 writer = 'response.write', 250 lexers = {}, 251 delimiters = ('{{','}}'), 252 _super_nodes = [], 253 ):
254 """ 255 text -- text to parse 256 context -- context to parse in 257 path -- folder path to templates 258 writer -- string of writer class to use 259 lexers -- dict of custom lexers to use. 260 delimiters -- for example ('{{','}}') 261 _super_nodes -- a list of nodes to check for inclusion 262 this should only be set by "self.extend" 263 It contains a list of SuperNodes from a child 264 template that need to be handled. 265 """ 266 267 # Keep a root level name. 268 self.name = name 269 # Raw text to start parsing. 270 self.text = text 271 # Writer to use (refer to the default for an example). 272 # This will end up as 273 # "%s(%s, escape=False)" % (self.writer, value) 274 self.writer = writer 275 276 # Dictionary of custom name lexers to use. 277 if isinstance(lexers, dict): 278 self.lexers = lexers 279 else: 280 self.lexers = {} 281 282 # Path of templates 283 self.path = path 284 # Context for templates. 285 self.context = context 286 287 # allow optional alternative delimiters 288 self.delimiters = delimiters 289 if delimiters != self.default_delimiters: 290 escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) 291 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) 292 elif context.has_key('response'): 293 if context['response'].delimiters != self.default_delimiters: 294 escaped_delimiters = (re.escape(context['response'].delimiters[0]), 295 re.escape(context['response'].delimiters[1])) 296 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL) 297 298 # Create a root level Content that everything will go into. 299 self.content = Content(name=name) 300 301 # Stack will hold our current stack of nodes. 302 # As we descend into a node, it will be added to the stack 303 # And when we leave, it will be removed from the stack. 304 # self.content should stay on the stack at all times. 305 self.stack = [self.content] 306 307 # This variable will hold a reference to every super block 308 # that we come across in this template. 309 self.super_nodes = [] 310 311 # This variable will hold a reference to the child 312 # super nodes that need handling. 313 self.child_super_nodes = _super_nodes 314 315 # This variable will hold a reference to every block 316 # that we come across in this template 317 self.blocks = {} 318 319 # Begin parsing. 320 self.parse(text)
321
322 - def to_string(self):
323 """ 324 Return the parsed template with correct indentation. 325 326 Used to make it easier to port to python3. 327 """ 328 return self.reindent(str(self.content))
329
330 - def __str__(self):
331 "Make sure str works exactly the same as python 3" 332 return self.to_string()
333
334 - def __unicode__(self):
335 "Make sure str works exactly the same as python 3" 336 return self.to_string()
337
338 - def reindent(self, text):
339 """ 340 Reindents a string of unindented python code. 341 """ 342 343 # Get each of our lines into an array. 344 lines = text.split('\n') 345 346 # Our new lines 347 new_lines = [] 348 349 # Keeps track of how many indents we have. 350 # Used for when we need to drop a level of indentation 351 # only to reindent on the next line. 352 credit = 0 353 354 # Current indentation 355 k = 0 356 357 ################# 358 # THINGS TO KNOW 359 ################# 360 361 # k += 1 means indent 362 # k -= 1 means unindent 363 # credit = 1 means unindent on the next line. 364 365 for raw_line in lines: 366 line = raw_line.strip() 367 368 # ignore empty lines 369 if not line: 370 continue 371 372 # If we have a line that contains python code that 373 # should be unindented for this line of code. 374 # and then reindented for the next line. 375 if TemplateParser.re_block.match(line): 376 k = k + credit - 1 377 378 # We obviously can't have a negative indentation 379 k = max(k,0) 380 381 # Add the indentation! 382 new_lines.append(' '*(4*k)+line) 383 384 # Bank account back to 0 again :( 385 credit = 0 386 387 # If we are a pass block, we obviously de-dent. 388 if TemplateParser.re_pass.match(line): 389 k -= 1 390 391 # If we are any of the following, de-dent. 392 # However, we should stay on the same level 393 # But the line right after us will be de-dented. 394 # So we add one credit to keep us at the level 395 # while moving back one indentation level. 396 if TemplateParser.re_unblock.match(line): 397 credit = 1 398 k -= 1 399 400 # If we are an if statement, a try, or a semi-colon we 401 # probably need to indent the next line. 402 if line.endswith(':') and not line.startswith('#'): 403 k += 1 404 405 # This must come before so that we can raise an error with the 406 # right content. 407 new_text = '\n'.join(new_lines) 408 409 if k > 0: 410 self._raise_error('missing "pass" in view', new_text) 411 elif k < 0: 412 self._raise_error('too many "pass" in view', new_text) 413 414 return new_text
415
416 - def _raise_error(self, message='', text=None):
417 """ 418 Raise an error using itself as the filename and textual content. 419 """ 420 raise RestrictedError(self.name, text or self.text, message)
421
422 - def _get_file_text(self, filename):
423 """ 424 Attempt to open ``filename`` and retrieve its text. 425 426 This will use self.path to search for the file. 427 """ 428 429 # If they didn't specify a filename, how can we find one! 430 if not filename.strip(): 431 self._raise_error('Invalid template filename') 432 433 # Get the filename; filename looks like ``"template.html"``. 434 # We need to eval to remove the quotes and get the string type. 435 filename = eval(filename, self.context) 436 437 # Get the path of the file on the system. 438 filepath = os.path.join(self.path, filename) 439 440 # try to read the text. 441 try: 442 fileobj = open(filepath, 'rb') 443 text = fileobj.read() 444 fileobj.close() 445 except IOError: 446 self._raise_error('Unable to open included view file: ' + filepath) 447 448 return text
449
450 - def include(self, content, filename):
451 """ 452 Include ``filename`` here. 453 """ 454 text = self._get_file_text(filename) 455 456 t = TemplateParser(text, 457 name = filename, 458 context = self.context, 459 path = self.path, 460 writer = self.writer, 461 delimiters = self.delimiters) 462 463 content.append(t.content)
464
465 - def extend(self, filename):
466 """ 467 Extend ``filename``. Anything not declared in a block defined by the 468 parent will be placed in the parent templates ``{{include}}`` block. 469 """ 470 text = self._get_file_text(filename) 471 472 # Create out nodes list to send to the parent 473 super_nodes = [] 474 # We want to include any non-handled nodes. 475 super_nodes.extend(self.child_super_nodes) 476 # And our nodes as well. 477 super_nodes.extend(self.super_nodes) 478 479 t = TemplateParser(text, 480 name = filename, 481 context = self.context, 482 path = self.path, 483 writer = self.writer, 484 delimiters = self.delimiters, 485 _super_nodes = super_nodes) 486 487 # Make a temporary buffer that is unique for parent 488 # template. 489 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) 490 pre = [] 491 492 # Iterate through each of our nodes 493 for node in self.content.nodes: 494 # If a node is a block 495 if isinstance(node, BlockNode): 496 # That happens to be in the parent template 497 if node.name in t.content.blocks: 498 # Do not include it 499 continue 500 501 if isinstance(node, Node): 502 # Or if the node was before the extension 503 # we should not include it 504 if node.pre_extend: 505 pre.append(node) 506 continue 507 508 # Otherwise, it should go int the 509 # Parent templates {{include}} section. 510 buf.append(node) 511 else: 512 buf.append(node) 513 514 # Clear our current nodes. We will be replacing this with 515 # the parent nodes. 516 self.content.nodes = [] 517 518 # Set our include, unique by filename 519 t.content.blocks['__include__' + filename] = buf 520 521 # Make sure our pre_extended nodes go first 522 t.content.insert(pre) 523 524 # Then we extend our blocks 525 t.content.extend(self.content) 526 527 # Work off the parent node. 528 self.content = t.content
529
530 - def parse(self, text):
531 532 # Basically, r_tag.split will split the text into 533 # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' 534 # so if we alternate this variable, we know 535 # what to look for. This is alternate to 536 # line.startswith("{{") 537 in_tag = False 538 extend = None 539 pre_extend = True 540 541 # Use a list to store everything in 542 # This is because later the code will "look ahead" 543 # for missing strings or brackets. 544 ij = self.r_tag.split(text) 545 # j = current index 546 # i = current item 547 for j in range(len(ij)): 548 i = ij[j] 549 550 if i: 551 if len(self.stack) == 0: 552 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') 553 554 # Our current element in the stack. 555 top = self.stack[-1] 556 557 if in_tag: 558 line = i 559 560 # If we are missing any strings!!!! 561 # This usually happens with the following example 562 # template code 563 # 564 # {{a = '}}'}} 565 # or 566 # {{a = '}}blahblah{{'}} 567 # 568 # This will fix these 569 # This is commented out because the current template 570 # system has this same limitation. Since this has a 571 # performance hit on larger templates, I do not recommend 572 # using this code on production systems. This is still here 573 # for "i told you it *can* be fixed" purposes. 574 # 575 # 576 # if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: 577 # 578 # # Look ahead 579 # la = 1 580 # nextline = ij[j+la] 581 # 582 # # As long as we have not found our ending 583 # # brackets keep going 584 # while '}}' not in nextline: 585 # la += 1 586 # nextline += ij[j+la] 587 # # clear this line, so we 588 # # don't attempt to parse it 589 # # this is why there is an "if i" 590 # # around line 530 591 # ij[j+la] = '' 592 # 593 # # retrieve our index. 594 # index = nextline.index('}}') 595 # 596 # # Everything before the new brackets 597 # before = nextline[:index+2] 598 # 599 # # Everything after 600 # after = nextline[index+2:] 601 # 602 # # Make the next line everything after 603 # # so it parses correctly, this *should* be 604 # # all html 605 # ij[j+1] = after 606 # 607 # # Add everything before to the current line 608 # line += before 609 610 # Get rid of '{{' and '}}' 611 line = line[2:-2].strip() 612 613 # This is bad juju, but let's do it anyway 614 if not line: 615 continue 616 617 # We do not want to replace the newlines in code, 618 # only in block comments. 619 def remove_newline(re_val): 620 # Take the entire match and replace newlines with 621 # escaped newlines. 622 return re_val.group(0).replace('\n', '\\n')
623 624 # Perform block comment escaping. 625 # This performs escaping ON anything 626 # in between """ and """ 627 line = re.sub(TemplateParser.r_multiline, 628 remove_newline, 629 line) 630 631 if line.startswith('='): 632 # IE: {{=response.title}} 633 name, value = '=', line[1:].strip() 634 else: 635 v = line.split(' ', 1) 636 if len(v) == 1: 637 # Example 638 # {{ include }} 639 # {{ end }} 640 name = v[0] 641 value = '' 642 else: 643 # Example 644 # {{ block pie }} 645 # {{ include "layout.html" }} 646 # {{ for i in range(10): }} 647 name = v[0] 648 value = v[1] 649 650 # This will replace newlines in block comments 651 # with the newline character. This is so that they 652 # retain their formatting, but squish down to one 653 # line in the rendered template. 654 655 # First check if we have any custom lexers 656 if name in self.lexers: 657 # Pass the information to the lexer 658 # and allow it to inject in the environment 659 660 # You can define custom names such as 661 # '{{<<variable}}' which could potentially 662 # write unescaped version of the variable. 663 self.lexers[name](parser = self, 664 value = value, 665 top = top, 666 stack = self.stack,) 667 668 elif name == '=': 669 # So we have a variable to insert into 670 # the template 671 buf = "\n%s(%s)" % (self.writer, value) 672 top.append(Node(buf, pre_extend = pre_extend)) 673 674 elif name == 'block' and not value.startswith('='): 675 # Make a new node with name. 676 node = BlockNode(name = value.strip(), 677 pre_extend = pre_extend, 678 delimiters = self.delimiters) 679 680 # Append this node to our active node 681 top.append(node) 682 683 # Make sure to add the node to the stack. 684 # so anything after this gets added 685 # to this node. This allows us to 686 # "nest" nodes. 687 self.stack.append(node) 688 689 elif name == 'end' and not value.startswith('='): 690 # We are done with this node. 691 692 # Save an instance of it 693 self.blocks[top.name] = top 694 695 # Pop it. 696 self.stack.pop() 697 698 elif name == 'super' and not value.startswith('='): 699 # Get our correct target name 700 # If they just called {{super}} without a name 701 # attempt to assume the top blocks name. 702 if value: 703 target_node = value 704 else: 705 target_node = top.name 706 707 # Create a SuperNode instance 708 node = SuperNode(name = target_node, 709 pre_extend = pre_extend) 710 711 # Add this to our list to be taken care of 712 self.super_nodes.append(node) 713 714 # And put in in the tree 715 top.append(node) 716 717 elif name == 'include' and not value.startswith('='): 718 # If we know the target file to include 719 if value: 720 self.include(top, value) 721 722 # Otherwise, make a temporary include node 723 # That the child node will know to hook into. 724 else: 725 include_node = BlockNode(name = '__include__' + self.name, 726 pre_extend = pre_extend, 727 delimiters = self.delimiters) 728 top.append(include_node) 729 730 elif name == 'extend' and not value.startswith('='): 731 # We need to extend the following 732 # template. 733 extend = value 734 pre_extend = False 735 736 else: 737 # If we don't know where it belongs 738 # we just add it anyways without formatting. 739 if line and in_tag: 740 741 # Split on the newlines >.< 742 tokens = line.split('\n') 743 744 # We need to look for any instances of 745 # for i in range(10): 746 # = i 747 # pass 748 # So we can properly put a response.write() in place. 749 continuation = False 750 len_parsed = 0 751 for k in range(len(tokens)): 752 753 tokens[k] = tokens[k].strip() 754 len_parsed += len(tokens[k]) 755 756 if tokens[k].startswith('='): 757 if tokens[k].endswith('\\'): 758 continuation = True 759 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) 760 else: 761 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) 762 elif continuation: 763 tokens[k] += ')' 764 continuation = False 765 766 767 buf = "\n%s" % '\n'.join(tokens) 768 top.append(Node(buf, pre_extend = pre_extend)) 769 770 else: 771 # It is HTML so just include it. 772 buf = "\n%s(%r, escape=False)" % (self.writer, i) 773 top.append(Node(buf, pre_extend = pre_extend)) 774 775 # Remember: tag, not tag, tag, not tag 776 in_tag = not in_tag 777 778 # Make a list of items to remove from child 779 to_rm = [] 780 781 # Go through each of the children nodes 782 for node in self.child_super_nodes: 783 # If we declared a block that this node wants to include 784 if node.name in self.blocks: 785 # Go ahead and include it! 786 node.value = self.blocks[node.name] 787 # Since we processed this child, we don't need to 788 # pass it along to the parent 789 to_rm.append(node) 790 791 # Remove some of the processed nodes 792 for node in to_rm: 793 # Since this is a pointer, it works beautifully. 794 # Sometimes I miss C-Style pointers... I want my asterisk... 795 self.child_super_nodes.remove(node) 796 797 # If we need to extend a template. 798 if extend: 799 self.extend(extend)
800 801 # We need this for integration with gluon
802 -def parse_template(filename, 803 path = 'views/', 804 context = dict(), 805 lexers = {}, 806 delimiters = ('{{','}}') 807 ):
808 """ 809 filename can be a view filename in the views folder or an input stream 810 path is the path of a views folder 811 context is a dictionary of symbols used to render the template 812 """ 813 814 # First, if we have a str try to open the file 815 if isinstance(filename, str): 816 try: 817 fp = open(os.path.join(path, filename), 'rb') 818 text = fp.read() 819 fp.close() 820 except IOError: 821 raise RestrictedError(filename, '', 'Unable to find the file') 822 else: 823 text = filename.read() 824 825 # Use the file contents to get a parsed template and return it. 826 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
827
828 -def get_parsed(text):
829 """ 830 Returns the indented python code of text. Useful for unit testing. 831 832 """ 833 return str(TemplateParser(text))
834 835 # And this is a generic render function. 836 # Here for integration with gluon.
837 -def render(content = "hello world", 838 stream = None, 839 filename = None, 840 path = None, 841 context = {}, 842 lexers = {}, 843 delimiters = ('{{','}}') 844 ):
845 """ 846 >>> render() 847 'hello world' 848 >>> render(content='abc') 849 'abc' 850 >>> render(content='abc\\'') 851 "abc'" 852 >>> render(content='a"\\'bc') 853 'a"\\'bc' 854 >>> render(content='a\\nbc') 855 'a\\nbc' 856 >>> render(content='a"bcd"e') 857 'a"bcd"e' 858 >>> render(content="'''a\\nc'''") 859 "'''a\\nc'''" 860 >>> render(content="'''a\\'c'''") 861 "'''a\'c'''" 862 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) 863 '0<br />1<br />2<br />3<br />4<br />' 864 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) 865 '0<br />1<br />2<br />3<br />4<br />' 866 >>> render(content="{{='''hello\\nworld'''}}") 867 'hello\\nworld' 868 >>> render(content='{{for i in range(3):\\n=i\\npass}}') 869 '012' 870 """ 871 # Here to avoid circular Imports 872 try: 873 from globals import Response 874 except: 875 # Working standalone. Build a mock Response object. 876 class Response(): 877 def __init__(self): 878 self.body = cStringIO.StringIO()
879 def write(self, data, escape=True): 880 if not escape: 881 self.body.write(str(data)) 882 elif hasattr(data,'xml') and callable(data.xml): 883 self.body.write(data.xml()) 884 else: 885 # make it a string 886 if not isinstance(data, (str, unicode)): 887 data = str(data) 888 elif isinstance(data, unicode): 889 data = data.encode('utf8', 'xmlcharrefreplace') 890 data = cgi.escape(data, True).replace("'","&#x27;") 891 self.body.write(data) 892 893 # A little helper to avoid escaping. 894 class NOESCAPE(): 895 def __init__(self, text): 896 self.text = text 897 def xml(self): 898 return self.text 899 # Add it to the context so we can use it. 900 context['NOESCAPE'] = NOESCAPE 901 902 # If we don't have anything to render, why bother? 903 if not content and not stream and not filename: 904 raise SyntaxError, "Must specify a stream or filename or content" 905 906 # Here for legacy purposes, probably can be reduced to something more simple. 907 close_stream = False 908 if not stream: 909 if filename: 910 stream = open(filename, 'rb') 911 close_stream = True 912 elif content: 913 stream = cStringIO.StringIO(content) 914 915 # Get a response class. 916 context['response'] = Response() 917 918 # Execute the template. 919 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) 920 try: 921 exec(code) in context 922 except Exception: 923 # for i,line in enumerate(code.split('\n')): print i,line 924 raise 925 926 if close_stream: 927 stream.close() 928 929 # Returned the rendered content. 930 return context['response'].body.getvalue() 931 932 933 if __name__ == '__main__': 934 import doctest 935 doctest.testmod() 936