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