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 raise restricted.RestrictedError(self.name, text or self.text, message)
410
411 - def _get_file_text(self, filename):
412 """ 413 Attempt to open ``filename`` and retrieve its text. 414 415 This will use self.path to search for the file. 416 """ 417 418 # If they didn't specify a filename, how can we find one! 419 if not filename.strip(): 420 self._raise_error('Invalid template filename') 421 422 # Get the filename; filename looks like ``"template.html"``. 423 # We need to eval to remove the quotes and get the string type. 424 filename = eval(filename, self.context) 425 426 # Get the path of the file on the system. 427 filepath = os.path.join(self.path, filename) 428 429 # try to read the text. 430 try: 431 fileobj = open(filepath, 'rb') 432 433 text = fileobj.read() 434 435 fileobj.close() 436 except IOError: 437 self._raise_error('Unable to open included view file: ' + filepath) 438 439 return text
440
441 - def include(self, content, filename):
442 """ 443 Include ``filename`` here. 444 """ 445 text = self._get_file_text(filename) 446 447 t = TemplateParser(text, 448 name = filename, 449 context = self.context, 450 path = self.path, 451 writer = self.writer, 452 delimiters = self.delimiters) 453 454 content.append(t.content)
455
456 - def extend(self, filename):
457 """ 458 Extend ``filename``. Anything not declared in a block defined by the 459 parent will be placed in the parent templates ``{{include}}`` block. 460 """ 461 text = self._get_file_text(filename) 462 463 # Create out nodes list to send to the parent 464 super_nodes = [] 465 # We want to include any non-handled nodes. 466 super_nodes.extend(self.child_super_nodes) 467 # And our nodes as well. 468 super_nodes.extend(self.super_nodes) 469 470 t = TemplateParser(text, 471 name = filename, 472 context = self.context, 473 path = self.path, 474 writer = self.writer, 475 delimiters = self.delimiters, 476 _super_nodes = super_nodes) 477 478 # Make a temporary buffer that is unique for parent 479 # template. 480 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) 481 pre = [] 482 483 # Iterate through each of our nodes 484 for node in self.content.nodes: 485 # If a node is a block 486 if isinstance(node, BlockNode): 487 # That happens to be in the parent template 488 if node.name in t.content.blocks: 489 # Do not include it 490 continue 491 492 if isinstance(node, Node): 493 # Or if the node was before the extension 494 # we should not include it 495 if node.pre_extend: 496 pre.append(node) 497 continue 498 499 # Otherwise, it should go int the 500 # Parent templates {{include}} section. 501 buf.append(node) 502 else: 503 buf.append(node) 504 505 # Clear our current nodes. We will be replacing this with 506 # the parent nodes. 507 self.content.nodes = [] 508 509 # Set our include, unique by filename 510 t.content.blocks['__include__' + filename] = buf 511 512 # Make sure our pre_extended nodes go first 513 t.content.insert(pre) 514 515 # Then we extend our blocks 516 t.content.extend(self.content) 517 518 # Work off the parent node. 519 self.content = t.content
520
521 - def parse(self, text):
522 523 # Basically, r_tag.split will split the text into 524 # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' 525 # so if we alternate this variable, we know 526 # what to look for. This is alternate to 527 # line.startswith("{{") 528 in_tag = False 529 extend = None 530 pre_extend = True 531 532 # Use a list to store everything in 533 # This is because later the code will "look ahead" 534 # for missing strings or brackets. 535 ij = self.r_tag.split(text) 536 # j = current index 537 # i = current item 538 for j in range(len(ij)): 539 i = ij[j] 540 541 if i: 542 if len(self.stack) == 0: 543 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') 544 545 # Our current element in the stack. 546 top = self.stack[-1] 547 548 if in_tag: 549 line = i 550 551 # If we are missing any strings!!!! 552 # This usually happens with the following example 553 # template code 554 # 555 # {{a = '}}'}} 556 # or 557 # {{a = '}}blahblah{{'}} 558 # 559 # This will fix these 560 # This is commented out because the current template 561 # system has this same limitation. Since this has a 562 # performance hit on larger templates, I do not recommend 563 # using this code on production systems. This is still here 564 # for "i told you it *can* be fixed" purposes. 565 # 566 # 567 # if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: 568 # 569 # # Look ahead 570 # la = 1 571 # nextline = ij[j+la] 572 # 573 # # As long as we have not found our ending 574 # # brackets keep going 575 # while '}}' not in nextline: 576 # la += 1 577 # nextline += ij[j+la] 578 # # clear this line, so we 579 # # don't attempt to parse it 580 # # this is why there is an "if i" 581 # # around line 530 582 # ij[j+la] = '' 583 # 584 # # retrieve our index. 585 # index = nextline.index('}}') 586 # 587 # # Everything before the new brackets 588 # before = nextline[:index+2] 589 # 590 # # Everything after 591 # after = nextline[index+2:] 592 # 593 # # Make the next line everything after 594 # # so it parses correctly, this *should* be 595 # # all html 596 # ij[j+1] = after 597 # 598 # # Add everything before to the current line 599 # line += before 600 601 # Get rid of '{{' and '}}' 602 line = line[2:-2].strip() 603 604 # This is bad juju, but let's do it anyway 605 if not line: 606 continue 607 608 # We do not want to replace the newlines in code, 609 # only in block comments. 610 def remove_newline(re_val): 611 # Take the entire match and replace newlines with 612 # escaped newlines. 613 return re_val.group(0).replace('\n', '\\n')
614 615 # Perform block comment escaping. 616 # This performs escaping ON anything 617 # in between """ and """ 618 line = re.sub(TemplateParser.r_multiline, 619 remove_newline, 620 line) 621 622 if line.startswith('='): 623 # IE: {{=response.title}} 624 name, value = '=', line[1:].strip() 625 else: 626 v = line.split(' ', 1) 627 if len(v) == 1: 628 # Example 629 # {{ include }} 630 # {{ end }} 631 name = v[0] 632 value = '' 633 else: 634 # Example 635 # {{ block pie }} 636 # {{ include "layout.html" }} 637 # {{ for i in range(10): }} 638 name = v[0] 639 value = v[1] 640 641 # This will replace newlines in block comments 642 # with the newline character. This is so that they 643 # retain their formatting, but squish down to one 644 # line in the rendered template. 645 646 # First check if we have any custom lexers 647 if name in self.lexers: 648 # Pass the information to the lexer 649 # and allow it to inject in the environment 650 651 # You can define custom names such as 652 # '{{<<variable}}' which could potentially 653 # write unescaped version of the variable. 654 self.lexers[name](parser = self, 655 value = value, 656 top = top, 657 stack = self.stack,) 658 659 elif name == '=': 660 # So we have a variable to insert into 661 # the template 662 buf = "\n%s(%s)" % (self.writer, value) 663 top.append(Node(buf, pre_extend = pre_extend)) 664 665 elif name == 'block' and not value.startswith('='): 666 # Make a new node with name. 667 node = BlockNode(name = value.strip(), 668 pre_extend = pre_extend, 669 delimiters = self.delimiters) 670 671 # Append this node to our active node 672 top.append(node) 673 674 # Make sure to add the node to the stack. 675 # so anything after this gets added 676 # to this node. This allows us to 677 # "nest" nodes. 678 self.stack.append(node) 679 680 elif name == 'end' and not value.startswith('='): 681 # We are done with this node. 682 683 # Save an instance of it 684 self.blocks[top.name] = top 685 686 # Pop it. 687 self.stack.pop() 688 689 elif name == 'super' and not value.startswith('='): 690 # Get our correct target name 691 # If they just called {{super}} without a name 692 # attempt to assume the top blocks name. 693 if value: 694 target_node = value 695 else: 696 target_node = top.name 697 698 # Create a SuperNode instance 699 node = SuperNode(name = target_node, 700 pre_extend = pre_extend) 701 702 # Add this to our list to be taken care of 703 self.super_nodes.append(node) 704 705 # And put in in the tree 706 top.append(node) 707 708 elif name == 'include' and not value.startswith('='): 709 # If we know the target file to include 710 if value: 711 self.include(top, value) 712 713 # Otherwise, make a temporary include node 714 # That the child node will know to hook into. 715 else: 716 include_node = BlockNode(name = '__include__' + self.name, 717 pre_extend = pre_extend, 718 delimiters = self.delimiters) 719 top.append(include_node) 720 721 elif name == 'extend' and not value.startswith('='): 722 # We need to extend the following 723 # template. 724 extend = value 725 pre_extend = False 726 727 else: 728 # If we don't know where it belongs 729 # we just add it anyways without formatting. 730 if line and in_tag: 731 732 # Split on the newlines >.< 733 tokens = line.split('\n') 734 735 # We need to look for any instances of 736 # for i in range(10): 737 # = i 738 # pass 739 # So we can properly put a response.write() in place. 740 continuation = False 741 len_parsed = 0 742 for k in range(len(tokens)): 743 744 tokens[k] = tokens[k].strip() 745 len_parsed += len(tokens[k]) 746 747 if tokens[k].startswith('='): 748 if tokens[k].endswith('\\'): 749 continuation = True 750 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) 751 else: 752 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) 753 elif continuation: 754 tokens[k] += ')' 755 continuation = False 756 757 758 buf = "\n%s" % '\n'.join(tokens) 759 top.append(Node(buf, pre_extend = pre_extend)) 760 761 else: 762 # It is HTML so just include it. 763 buf = "\n%s(%r, escape=False)" % (self.writer, i) 764 top.append(Node(buf, pre_extend = pre_extend)) 765 766 # Remember: tag, not tag, tag, not tag 767 in_tag = not in_tag 768 769 # Make a list of items to remove from child 770 to_rm = [] 771 772 # Go through each of the children nodes 773 for node in self.child_super_nodes: 774 # If we declared a block that this node wants to include 775 if node.name in self.blocks: 776 # Go ahead and include it! 777 node.value = self.blocks[node.name] 778 # Since we processed this child, we don't need to 779 # pass it along to the parent 780 to_rm.append(node) 781 782 # Remove some of the processed nodes 783 for node in to_rm: 784 # Since this is a pointer, it works beautifully. 785 # Sometimes I miss C-Style pointers... I want my asterisk... 786 self.child_super_nodes.remove(node) 787 788 # If we need to extend a template. 789 if extend: 790 self.extend(extend)
791 792 # We need this for integration with gluon
793 -def parse_template(filename, 794 path = 'views/', 795 context = dict(), 796 lexers = {}, 797 delimiters = ('{{','}}') 798 ):
799 """ 800 filename can be a view filename in the views folder or an input stream 801 path is the path of a views folder 802 context is a dictionary of symbols used to render the template 803 """ 804 805 # First, if we have a str try to open the file 806 if isinstance(filename, str): 807 try: 808 fp = open(os.path.join(path, filename), 'rb') 809 text = fp.read() 810 fp.close() 811 except IOError: 812 raise restricted.RestrictedError(filename, '', 'Unable to find the file') 813 else: 814 text = filename.read() 815 816 # Use the file contents to get a parsed template and return it. 817 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
818
819 -def get_parsed(text):
820 """ 821 Returns the indented python code of text. Useful for unit testing. 822 823 """ 824 return str(TemplateParser(text))
825 826 # And this is a generic render function. 827 # Here for integration with gluon.
828 -def render(content = "hello world", 829 stream = None, 830 filename = None, 831 path = None, 832 context = {}, 833 lexers = {}, 834 delimiters = ('{{','}}') 835 ):
836 """ 837 >>> render() 838 'hello world' 839 >>> render(content='abc') 840 'abc' 841 >>> render(content='abc\\'') 842 "abc'" 843 >>> render(content='a"\\'bc') 844 'a"\\'bc' 845 >>> render(content='a\\nbc') 846 'a\\nbc' 847 >>> render(content='a"bcd"e') 848 'a"bcd"e' 849 >>> render(content="'''a\\nc'''") 850 "'''a\\nc'''" 851 >>> render(content="'''a\\'c'''") 852 "'''a\'c'''" 853 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) 854 '0<br />1<br />2<br />3<br />4<br />' 855 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) 856 '0<br />1<br />2<br />3<br />4<br />' 857 >>> render(content="{{='''hello\\nworld'''}}") 858 'hello\\nworld' 859 >>> render(content='{{for i in range(3):\\n=i\\npass}}') 860 '012' 861 """ 862 # Here to avoid circular Imports 863 import globals 864 865 # If we don't have anything to render, why bother? 866 if not content and not stream and not filename: 867 raise SyntaxError, "Must specify a stream or filename or content" 868 869 # Here for legacy purposes, probably can be reduced to something more simple. 870 close_stream = False 871 if not stream: 872 if filename: 873 stream = open(filename, 'rb') 874 close_stream = True 875 elif content: 876 stream = cStringIO.StringIO(content) 877 878 # Get a response class. 879 context['response'] = globals.Response() 880 881 # Execute the template. 882 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) 883 try: 884 exec(code) in context 885 except Exception: 886 # for i,line in enumerate(code.split('\n')): print i,line 887 raise 888 889 if close_stream: 890 stream.close() 891 892 # Returned the rendered content. 893 return context['response'].body.getvalue()
894 895 896 if __name__ == '__main__': 897 import doctest 898 doctest.testmod() 899